diff --git a/src/fontra/client/core/path-hit-tester.js b/src/fontra/client/core/path-hit-tester.js index f6c89d4482..56d0f7e671 100644 --- a/src/fontra/client/core/path-hit-tester.js +++ b/src/fontra/client/core/path-hit-tester.js @@ -62,7 +62,7 @@ export class PathHitTester { } } - results = results.filter((hit) => hit.t != 0 && hit.t != 1); + // results = results.filter((hit) => hit.t != 0 && hit.t != 1); results.sort((a, b) => a.d - b.d); return results[0]; } diff --git a/src/fontra/views/editor/edit-tools-pointer.js b/src/fontra/views/editor/edit-tools-pointer.js index 4bab67aa2e..7e1e8f01c3 100644 --- a/src/fontra/views/editor/edit-tools-pointer.js +++ b/src/fontra/views/editor/edit-tools-pointer.js @@ -58,6 +58,7 @@ export class PointerTool extends BaseTool { sceneController.hoverSelection = selection; sceneController.hoveredGlyph = undefined; sceneController.hoverPathHit = pathHit; + sceneController.magicSelectionHit = undefined; if (!sceneController.hoverSelection.size && !sceneController.hoverPathHit) { sceneController.hoveredGlyph = this.sceneModel.glyphAtPoint(point); @@ -113,6 +114,44 @@ export class PointerTool extends BaseTool { } } + selectContour(sceneController, contourIndex, selectModeFunction) { + const instance = this.sceneModel.getSelectedPositionedGlyph().glyph.instance; + const startPoint = instance.path.getAbsolutePointIndex(contourIndex, 0); + const endPoint = instance.path.contourInfo[contourIndex].endPoint; + const newSelection = new Set(); + for (const i of range(startPoint, endPoint + 1)) { + const pointType = instance.path.pointTypes[i] & VarPackedPath.POINT_TYPE_MASK; + if (pointType === VarPackedPath.ON_CURVE) { + newSelection.add(`point/${i}`); + } + } + const selection = this._selectionBeforeSingleClick || sceneController.selection; + this._selectionBeforeSingleClick = undefined; + const modeFunc = selectModeFunction(event); + sceneController.selection = modeFunc(selection, newSelection); + } + + getNearestHit(sceneController, event, glyphController) { + const positionedGlyph = this.sceneModel.getSelectedPositionedGlyph(); + const point = sceneController.localPoint(event); + point.x -= positionedGlyph.x; + point.y -= positionedGlyph.y; + const pathHitTester = glyphController.pathHitTester; + const nearestHit = pathHitTester.findNearest(point); + if (nearestHit) { + sceneController.magicSelectionHit = [ + point.x, + point.y, + nearestHit.x, + nearestHit.y, + ]; + const contourIndex = nearestHit.contourIndex; + this.selectContour(sceneController, contourIndex, getMagicSelectModeFunction); + } else { + sceneController.magicSelectionHit = undefined; + } + } + async handleDrag(eventStream, initialEvent) { const sceneController = this.sceneController; const initialSelection = sceneController.selection; @@ -201,15 +240,23 @@ export class PointerTool extends BaseTool { } } - sceneController.hoveredGlyph = undefined; - if (initiateRectSelect) { - return await this.handleRectSelect(eventStream, initialEvent, initialSelection); - } else if (initiateDrag) { - this.sceneController.sceneModel.initialClickedPointIndex = - initialClickedPointIndex; - const result = await this.handleDragSelection(eventStream, initialEvent); - delete this.sceneController.sceneModel.initialClickedPointIndex; - return result; + if (initialEvent.metaKey && !initialClickedPointIndex) { + const glyphController = await this.sceneModel.getSelectedStaticGlyphController(); + this.getNearestHit(sceneController, initialEvent, glyphController); + if (initiateRectSelect) { + return await this.handleMagicSelect(eventStream, sceneController); + } + } else { + sceneController.hoveredGlyph = undefined; + if (initiateRectSelect) { + return await this.handleRectSelect(eventStream, initialEvent, initialSelection); + } else if (initiateDrag) { + this.sceneController.sceneModel.initialClickedPointIndex = + initialClickedPointIndex; + const result = await this.handleDragSelection(eventStream, initialEvent); + delete this.sceneController.sceneModel.initialClickedPointIndex; + return result; + } } } @@ -259,19 +306,7 @@ export class PointerTool extends BaseTool { await this.handlePointsDoubleClick(pointIndices); } else if (sceneController.hoverPathHit) { const contourIndex = sceneController.hoverPathHit.contourIndex; - const startPoint = instance.path.getAbsolutePointIndex(contourIndex, 0); - const endPoint = instance.path.contourInfo[contourIndex].endPoint; - const newSelection = new Set(); - for (const i of range(startPoint, endPoint + 1)) { - const pointType = instance.path.pointTypes[i] & VarPackedPath.POINT_TYPE_MASK; - if (pointType === VarPackedPath.ON_CURVE) { - newSelection.add(`point/${i}`); - } - } - const selection = this._selectionBeforeSingleClick || sceneController.selection; - this._selectionBeforeSingleClick = undefined; - const modeFunc = getSelectModeFunction(event); - sceneController.selection = modeFunc(selection, newSelection); + this.selectContour(sceneController, contourIndex, getSelectModeFunction); } } } @@ -319,6 +354,18 @@ export class PointerTool extends BaseTool { this._selectionBeforeSingleClick = undefined; } + async handleMagicSelect(eventStream, sceneController) { + const glyphController = await this.sceneModel.getSelectedStaticGlyphController(); + for await (const event of eventStream) { + if (event.metaKey) { + this.getNearestHit(sceneController, event, glyphController); + } else { + sceneController.magicSelectionHit = undefined; + } + } + sceneController.magicSelectionHit = undefined; + } + async handleDragSelection(eventStream, initialEvent) { this.sceneController.sceneModel.showTransformSelection = false; this._selectionBeforeSingleClick = undefined; @@ -695,6 +742,16 @@ function getSelectModeFunction(event) { : replace; } +function getMagicSelectModeFunction(event) { + return event.shiftKey + ? event[commandKeyProperty] + ? union + : symmetricDifference + : event[commandKeyProperty] + ? replace + : union; +} + registerVisualizationLayerDefinition({ identifier: "fontra.transform.selection", name: "Transform selection", diff --git a/src/fontra/views/editor/scene-controller.js b/src/fontra/views/editor/scene-controller.js index fb9855a0bf..bf10744c59 100644 --- a/src/fontra/views/editor/scene-controller.js +++ b/src/fontra/views/editor/scene-controller.js @@ -831,6 +831,15 @@ export class SceneController { this.canvasController.requestUpdate(); } + get magicSelectionHit() { + return this.sceneModel.magicSelectionHit; + } + + set magicSelectionHit(magicSelHit) { + this.sceneModel.magicSelectionHit = magicSelHit; + this.canvasController.requestUpdate(); + } + get backgroundLayers() { return this.sceneModel.backgroundLayers || []; } diff --git a/src/fontra/views/editor/visualization-layer-definitions.js b/src/fontra/views/editor/visualization-layer-definitions.js index 89937ff3e4..bcff81d2e2 100644 --- a/src/fontra/views/editor/visualization-layer-definitions.js +++ b/src/fontra/views/editor/visualization-layer-definitions.js @@ -1545,6 +1545,29 @@ registerVisualizationLayerDefinition({ }, }); +registerVisualizationLayerDefinition({ + identifier: "fontra.magic.select", + name: "Magic select", + selectionMode: "editing", + zIndex: 500, + screenParameters: { + strokeWidth: 1, + lineDash: [10, 10], + }, + draw: (context, positionedGlyph, parameters, model, controller) => { + if (model.magicSelectionHit === undefined) { + return; + } + const magicSelHit = model.magicSelectionHit; + const p1x = magicSelHit[0]; + const p1y = magicSelHit[1]; + const p2x = magicSelHit[2]; + const p2y = magicSelHit[3]; + context.lineWidth = parameters.strokeWidth; + strokeLineDashed(context, p1x, p1y, p2x, p2y); + }, +}); + registerVisualizationLayerDefinition({ identifier: "fontra.rect.select", name: "Rect select",