Skip to content

Add CoordSysLassoSelector for free-form, even-odd coord-space selection#73

Merged
hageldave merged 8 commits into
masterfrom
copilot/implement-coord-sys-lasso-selector
Jun 2, 2026
Merged

Add CoordSysLassoSelector for free-form, even-odd coord-space selection#73
hageldave merged 8 commits into
masterfrom
copilot/implement-coord-sys-lasso-selector

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Jun 1, 2026

This PR adds a new interaction primitive for CoordSysRenderer: drag-to-draw free-form lasso selection in coord space, finalized on mouse release and non-editable thereafter. The selected region is represented as Path2D with WIND_EVEN_ODD so self-intersections resolve by boundary parity (contains(...) behaves correctly for odd-crossing interior).

  • New selector class

    • Introduces CoordSysLassoSelector in hageldave.jplotter.interaction.kml.
    • Class hierarchy: CoordSysLassoSelector extends CoordSysViewSelector implements KeyListener.
    • Default key mask is VK_L (customizable via constructor KeyMaskListener).
  • Selection lifecycle + rendering

    • Keymask + press inside coord area starts a new lasso and clears prior finalized selection.
    • Drag appends points in coord space and renders live outline with a provisional close segment.
    • Release finalizes the closed outline, marks selection as persistent/non-editable, and emits areaSelected(Path2D).
    • Degenerate lassos (<3 points) are discarded.
  • Input handling details

    • Right-click clears via clearLasso().
    • ESC clears via dedicated escListener registered in register() / removed in deRegister().
    • Cursor is coordinated via CursorCoordinator only:
      • keymask + selection: HAND_CURSOR
      • keymask + no selection: CROSSHAIR_CURSOR
      • otherwise: release cursor request
  • Path semantics + drag decimation

    • Final path construction uses Path2D.Double(Path2D.WIND_EVEN_ODD) and closePath().
    • Drag point decimation applies a >2px AWT-distance threshold to reduce near-duplicate samples.
    • clearLasso() fully resets overlay/render/state and triggers areaCleared().
new CoordSysLassoSelector(canvas, coordsys) {
    @Override
    public void areaSelected(Path2D selectedArea) {
        // selectedArea uses WIND_EVEN_ODD
        // selectedArea.contains(x, y) handles self-intersections by odd crossing rule
    }
}.register();
Original prompt

Overview

Implement a new CoordSysLassoSelector class in hageldave.jplotter.interaction.kml that provides free-form lasso selection for the coordinate view of CoordSysRenderer. The user drags the mouse to draw an arbitrary closed shape; on mouse release the shape is finalised and not editable.

File to create

jplotter/src/main/java/hageldave/jplotter/interaction/kml/CoordSysLassoSelector.java

Class hierarchy

CoordSysLassoSelector extends CoordSysViewSelector implements KeyListener

CoordSysViewSelector is in the same package and provides: canvas, jPlotterCanvas, coordsys, overlay (CompleteRenderer), areaBorder (Lines), keyMaskListener, register(), deRegister().

Interaction model

User action Effect
Key mask + press mouse button inside coordsys area Clears any previous lasso; starts recording lasso points
Key mask + drag Continuously appends cursor position (coord-space) to the lasso path; renders live outline with a provisional "close" segment back to start
Release mouse button Closes path; fires areaSelected(Path2D); shape is final
Right-click Clears lasso (clearLasso())
ESC Clears lasso (clearLasso()) via a KeyAdapter escListener registered in register()

Key implementation details

State fields

protected boolean isDragging = false;
protected boolean hasSelection = false;
protected final List<Point2D.Double> lassoPoints = new ArrayList<>();
protected final Lines lassoLines = new Lines();
protected boolean isLassoInOverlay = false;

Point decimation in mouseDragged

Only append a new point if its AWT pixel distance from the previously recorded point is > 2px. This avoids thousands of near-duplicate points during slow/still drags.

Path construction — WIND_EVEN_ODD

private Path2D calculateSelectedArea() {
    Path2D path = new Path2D.Double(Path2D.WIND_EVEN_ODD);
    path.moveTo(lassoPoints.get(0).x, lassoPoints.get(0).y);
    for (int i = 1; i < lassoPoints.size(); i++)
        path.lineTo(lassoPoints.get(i).x, lassoPoints.get(i).y);
    path.closePath();  // automatically closes back to start
    return path;
}

Using WIND_EVEN_ODD means self-intersecting shapes are handled correctly: path.contains(x, y) returns true only if a ray from the point crosses the boundary an odd number of times.

mousePressed

  • Guard: keyMaskListener.areKeysPressed() AND click is inside coordsys.getCoordSysArea().
  • If right-click OR hasSelection is true: call clearLasso() and return.
  • Otherwise: set isDragging = true, clear lassoPoints, add first coord-space point, add lassoLines to overlay if not already there.

mouseDragged

  • Guard: isDragging must be true.
  • Clamp the AWT point to coordsys.getCoordSysArea() (same clamping logic as CoordSysViewSelector).
  • Transform clamped AWT point → coord-space via coordsys.transformAWT2CoordSys.
  • Decimation check: only add if AWT distance from last recorded AWT point > 2px.
  • Rebuild lassoLines: clear all segments, add segments for consecutive lassoPoints, add one preview-close segment from last point back to lassoPoints.get(0) using colorScheme.getColor2() or a distinct color.
  • Call areaSelectedOnGoing(calculateSelectedArea()) and jPlotterCanvas.scheduleRepaint().

mouseReleased

  • Guard: isDragging must be true.
  • Set isDragging = false.
  • If lassoPoints.size() < 3: call clearLasso() and return (degenerate selection).
  • Rebuild lassoLines final outline (solid close segment, same color as rest of outline).
  • Set hasSelection = true.
  • Call areaSelected(calculateSelectedArea()) and jPlotterCanvas.scheduleRepaint().

clearLasso()

public void clearLasso() {
    lassoLines.removeAllSegments();
    if (isLassoInOverlay) {
        overlay.lines.removeItemToRender(lassoLines);
        isLassoInOverlay = false;
    }
    lassoPoints.clear();
    isDragging = false;
    hasSelection = false;
    CursorCoordinator.get(canvas).requestCursor(null, this);
    jPlotterCanvas.scheduleRepaint();
    areaCleared();
}

Cursor

  • mouseMoved: if key mask pressed + hasSelectionHAND_CURSOR; if key mask pressed + no selection → CROSSHAIR_CURSOR; else release cursor via CursorCoordinator.get(canvas).requestCursor(null, this).
  • Also implement keyPressed / keyReleased from KeyListener to call the same cursor-update logic.
  • Use CursorCoordinator.get(canvas).requestCursor(...) throughout (never set cursor directly on canvas).

register() / deRegister()

  • register(): call super.register(), then add escListener and this (as KeyListener) to canvas if not already present.
  • deRegister(): call super.deRegister(), then remove escListener and this.
  • escListener is a KeyAdapter field: VK_ESCAPEclearLasso().

Abstract / overridable callbacks

// Final closed path; WIND_EVEN_ODD. path.contains(x,y) handles self-...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

*This pull request was created from Copilot chat.*
>

Copilot AI changed the title [WIP] Implement CoordSysLassoSelector for free-form lasso selection Add CoordSysLassoSelector for free-form, even-odd coord-space selection Jun 1, 2026
Copilot AI requested a review from hageldave June 1, 2026 09:09
@hageldave hageldave marked this pull request as ready for review June 2, 2026 09:09
@hageldave hageldave merged commit a2be939 into master Jun 2, 2026
1 check passed
@hageldave hageldave deleted the copilot/implement-coord-sys-lasso-selector branch June 2, 2026 09:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants