Skip to content

Instantly share code, notes, and snippets.

@Bryan-Legend
Created May 5, 2026 23:09
Show Gist options
  • Select an option

  • Save Bryan-Legend/1f7f7e952be7de913e6617ce2ae8deda to your computer and use it in GitHub Desktop.

Select an option

Save Bryan-Legend/1f7f7e952be7de913e6617ce2ae8deda to your computer and use it in GitHub Desktop.
ScenePointEditor.cs - Vectroid modifications (multiselect, drag-to-place insert, editor snap, prefab override fixes)
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using Object = UnityEngine.Object;
// Shapes © Freya Holmér - https://twitter.com/FreyaHolmer/
// Website & Documentation - https://acegikmo.com/shapes/
//
// MODIFIED for Vectroid:
// - Removed the AddRemovePoints Tab-cycle mode entirely; add/remove now happens via
// edge-hover ghost-button (Unity PolygonCollider2D-style insert) and the Delete key.
// - Added multiselect (shift-click to toggle selection), Ctrl/Cmd+D to duplicate
// selected points, and a combined position handle for moving multi-selections.
// - hasAddRemoveMode now gates the new add/remove behavior so fixed-count shapes
// (Line/Quad/Triangle) that explicitly set it to false still keep their counts.
namespace Shapes {
public class ScenePointEditor : SceneEditGizmos {
static bool isEditing;
// Public hook so EditorTool overlays (e.g. Polygon/Polyline edit tools)
// can drive the same edit-mode flag the inspector "Edit Points" button does.
public static bool IsEditingPoints {
get => isEditing;
set => isEditing = value;
}
public bool hasAddRemoveMode = true;
public bool hasEditThicknessMode = false;
public bool hasEditColorMode = false;
public bool useFlatThicknessHandles = false;
public event Action<ShapeRenderer, int> onValuesChanged = delegate { };
public bool[] colorEnabledArray = null;
public bool[] positionEnabledArray = null;
EditMode currentEditMode = EditMode.PositionHandles;
readonly HashSet<int> selectedIndices = new HashSet<int>();
// Drag-from-ghost state. While the user is dragging a freshly inserted
// point, insertDragControlID owns GUIUtility.hotControl and
// insertDragPointIndex is the index in `points` that follows the cursor.
int insertDragControlID;
int insertDragPointIndex = -1;
const float EDGE_HOVER_PIXEL_THRESHOLD = 12f;
const float SELECT_DOT_GUI_RADIUS = 7f;
// Approx screen-space reach of a Unity position handle (dot + axis arrows).
// When the mouse is within this many pixels of any point, suppress the
// edge-insert ghost so it can't sit under a handle and steal the click.
const float POSITION_HANDLE_AVOID_PIXELS = 60f;
void GoToNextEditMode() {
while( true ) {
currentEditMode = (EditMode)( ( (int)currentEditMode + 1 ) % (int)EditMode.COUNT );
if( CanEdit( currentEditMode ) == false ) continue;
break;
}
}
bool CanEdit( EditMode mode ) {
switch( mode ) {
case EditMode.EditThickness: return hasEditThicknessMode;
case EditMode.EditColor: return hasEditColorMode;
default: return true;
}
}
bool HasAnyExtraMode => hasEditThicknessMode || hasEditColorMode;
enum EditMode {
PositionHandles,
EditThickness,
EditColor,
COUNT
}
SphereBoundsHandle discHandle = ShapesHandles.InitDiscHandle();
public ScenePointEditor( Editor parentEditor ) => this.parentEditor = parentEditor;
protected override bool IsEditing {
get => isEditing;
set => isEditing = value;
}
public bool DoSceneHandles( bool closed, Object component, List<PolylinePoint> points, Transform tf, float globalThicknessScale = 1f, Color globalColorTint = default ) {
void SetPt( int i, Vector3 pt ) {
PolylinePoint pp = points[i];
pp.point = pt;
points[i] = pp;
}
float GetThicknessWorld( int i ) {
float localThickness = points[i].thickness * globalThicknessScale;
return localThickness * tf.lossyScale.AvgComponentMagnitude();
}
void SetThicknessWorld( int i, float thicknessWorld ) {
PolylinePoint pp = points[i];
float localThickness = thicknessWorld / tf.lossyScale.AvgComponentMagnitude();
pp.thickness = localThickness / globalThicknessScale;
points[i] = pp;
}
Color GetNetColor( int i ) => points[i].color * globalColorTint;
Color GetColor( int i ) => points[i].color;
void SetColor( int i, Color color ) {
PolylinePoint p = points[i];
p.color = color;
points[i] = p;
}
return DoSceneHandles( closed, component, points, tf, i => points[i].point, p => p.point, SetPt, PolylinePoint.Lerp, GetThicknessWorld, SetThicknessWorld, GetNetColor, GetColor, SetColor, () => points.Count );
}
public bool DoSceneHandles( bool closed, Object component, List<Vector2> points, Transform tf ) {
return DoSceneHandles( closed, component, points, tf, i => points[i], p => p, ( i, p ) => points[i] = p, Vector2.LerpUnclamped );
}
public bool DoSceneHandles( bool closed, Object component, List<Vector3> points, Transform tf ) {
return DoSceneHandles( closed, component, points, tf, i => points[i], p => p, ( i, p ) => points[i] = p, Vector3.LerpUnclamped );
}
public bool DoSceneHandles( bool closed, Object component, List<Vector3> points, List<Color> colors, Transform tf ) {
void SetColor( int i, Color c ) {
colors[i] = c;
onValuesChanged( component as ShapeRenderer, i );
}
return DoSceneHandles( closed, component, points, tf, i => points[i], p => p, ( i, p ) => points[i] = p, Vector3.LerpUnclamped, null, null, i => colors[i], i => colors[i], SetColor, () => colors.Count );
}
bool TextureButton( Vector3 worldPos, Texture2D tex, float scale, bool fade = true ) {
Rect r = new Rect( 0, 0, tex.width * scale, tex.height * scale );
r.center = HandleUtility.WorldToGUIPoint( worldPos );
Vector2 mousePos = Event.current.mousePosition;
if( fade ) {
float t = Mathf.InverseLerp( 200, 64, Vector2.Distance( mousePos, r.center ) );
float a = Mathf.Lerp( 0.3f, 1f, t );
GUI.color = new Color( 1, 1, 1, a );
}
bool pressed = GUI.Button( r, tex, GUIStyle.none );
if( fade )
GUI.color = Color.white;
return pressed;
}
bool DoSceneHandles<T>( bool closed,
Object component,
List<T> points,
Transform tf,
Func<int, Vector3> GetPtAt,
Func<T, Vector3> GetPt,
Action<int, Vector3> SetPt,
Func<T, T, float, T> Lerp,
Func<int, float> GetThicknessWorld = null,
Action<int, float> SetThicknessWorld = null,
Func<int, Color> GetNetColor = null,
Func<int, Color> GetColor = null,
Action<int, Color> SetColor = null,
Func<int> ColorCount = null ) {
CheckForCancelEditAction();
if( IsHoldingAlt )
return false;
bool changed = false;
Vector3 GetWorldPt( int i ) => tf.TransformPoint( GetPtAt( i ) );
if( !isEditing )
return false;
// Drop selection indices that no longer exist (e.g. after external edits)
selectedIndices.RemoveWhere( idx => idx < 0 || idx >= points.Count );
if( Event.current.isKey && Event.current.keyCode == KeyCode.Tab && HasAnyExtraMode ) {
if( Event.current.type == EventType.KeyDown )
GoToNextEditMode();
Event.current.Use();
}
// Mode label overlay
if( Selection.gameObjects.Length > 0 && Selection.gameObjects[0] == ( (Component)component ).gameObject ) {
if( Event.current.type == EventType.MouseMove )
SceneView.lastActiveSceneView.Repaint();
if( HasAnyExtraMode ) {
Handles.BeginGUI();
Vector2 mousePos = Event.current.mousePosition;
Rect r = new Rect( mousePos.x + 32, mousePos.y, Screen.width, 128 );
string label = "Press Tab to cycle modes:";
void SelectLabel( string str, EditMode mode, bool exists = true ) {
if( !exists ) return;
label += mode == currentEditMode ? "\n> " + str : "\n " + str;
}
SelectLabel( "position", EditMode.PositionHandles );
SelectLabel( "thickness", EditMode.EditThickness, hasEditThicknessMode );
SelectLabel( "color", EditMode.EditColor, hasEditColorMode );
GUI.Label( r, label );
Handles.EndGUI();
}
}
Quaternion handleRotation = Tools.pivotRotation == PivotRotation.Global ? Quaternion.identity : tf.rotation;
if( currentEditMode == EditMode.EditThickness ) {
Transform camTf = SceneView.lastActiveSceneView.camera.transform;
Vector3 camPos = camTf.position;
Vector3 camUp = camTf.up;
for( int i = 0; i < points.Count; i++ ) {
discHandle.radius = GetThicknessWorld( i ) / 2f;
discHandle.center = Vector3.zero;
Vector3 discPos = GetWorldPt( i );
Vector3 dirToCamera = discPos - camPos;
Quaternion discRot = useFlatThicknessHandles ? tf.rotation : Quaternion.LookRotation( dirToCamera, camUp );
Matrix4x4 mtx = Matrix4x4.TRS( discPos, discRot, Vector3.one );
using( var chChk = new EditorGUI.ChangeCheckScope() ) {
using( new Handles.DrawingScope( ShapesHandles.GetHandleColor( GetNetColor( i ) ), mtx ) )
discHandle.DrawHandle();
if( chChk.changed ) {
changed = true;
Undo.RecordObject( component, "edit thickness" );
SetThicknessWorld( i, discHandle.radius * 2 );
break;
}
}
}
} else if( currentEditMode == EditMode.EditColor ) {
Handles.BeginGUI();
for( int i = 0; i < ColorCount(); i++ ) {
if( colorEnabledArray != null && colorEnabledArray[i] == false ) continue;
Vector3 ptWorld = GetWorldPt( i );
Color col = GetColor( i );
col.a = 1f;
GUI.color = col;
if( TextureButton( ptWorld, UIAssets.Instance.pointEditColor, 0.5f, fade: false ) ) {
int captured = i;
ShapesUI.ShowColorPicker( OnColorChanged, GetColor( i ) );
void OnColorChanged( Color c ) {
Undo.RecordObject( component, "modify color" );
SetColor( captured, c );
( component as ShapeRenderer )?.UpdateAllMaterialProperties();
( component as ShapeRenderer )?.UpdateMesh( force: true );
// Async callback — doesn't go through the DoSceneHandles
// changed-return path, so mark dirty + record overrides here.
MarkComponentChanged( component );
ShapesUI.RepaintAllSceneViews();
}
}
}
GUI.color = Color.white;
Handles.EndGUI();
} else if( currentEditMode == EditMode.PositionHandles ) {
changed |= DoPositionMode( closed, component, points, tf, GetWorldPt, GetPt, SetPt, Lerp );
}
// Undo.RecordObject on its own isn't enough to persist changes to a
// List<T>'s contents (e.g. inserting/removing/mutating points): Unity
// won't pick up the modification reliably and prefab instances won't
// register an override. Mark dirty + record prefab modifications here
// so edits survive scene/prefab saves.
if( changed )
MarkComponentChanged( component );
return changed;
}
static void MarkComponentChanged( Object component ) {
if( component == null ) return;
EditorUtility.SetDirty( component );
if( PrefabUtility.IsPartOfPrefabInstance( component ) )
PrefabUtility.RecordPrefabInstancePropertyModifications( component );
}
// Project the mouse ray onto the shape's local XY plane (in world space).
// 2D shapes live on tf.forward; this returns where the cursor "is" on that
// plane so a drag can place the point under the mouse.
static bool ProjectMouseOntoPlane( Transform tf, Vector2 mouseGui, out Vector3 worldPos ) {
Plane plane = new Plane( tf.forward, tf.position );
Ray ray = HandleUtility.GUIPointToWorldRay( mouseGui );
if( plane.Raycast( ray, out float dist ) ) {
worldPos = ray.GetPoint( dist );
return true;
}
worldPos = default;
return false;
}
static Vector3 SnapWorldIfEnabled( Vector3 worldPos ) {
if( !EditorSnapSettings.gridSnapEnabled ) return worldPos;
Vector3 step = EditorSnapSettings.move;
if( step.x <= 0f ) step.x = 1f;
if( step.y <= 0f ) step.y = 1f;
if( step.z <= 0f ) step.z = 1f;
return new Vector3(
Mathf.Round( worldPos.x / step.x ) * step.x,
Mathf.Round( worldPos.y / step.y ) * step.y,
Mathf.Round( worldPos.z / step.z ) * step.z
);
}
// Position-handle mode with multiselect, edge-insert, delete, duplicate.
bool DoPositionMode<T>( bool closed,
Object component,
List<T> points,
Transform tf,
Func<int, Vector3> GetWorldPt,
Func<T, Vector3> GetPt,
Action<int, Vector3> SetPt,
Func<T, T, float, T> Lerp ) {
bool changed = false;
Event e = Event.current;
Quaternion handleRotation = Tools.pivotRotation == PivotRotation.Global ? Quaternion.identity : tf.rotation;
int minPoints = closed ? 3 : 2;
// --- Click-based selection (event-level) ---
// The dot's Handles.Button can't see the click any more because the
// always-drawn position handle's center hit area sits on top of it. Handle
// shift+click ourselves before any Handles call so multiselect works.
// Plain clicks near a point also retarget the selection to that point so
// the dot color tracks whatever handle the user actually grabbed; the
// event isn't consumed in that case so the position handle still drags.
if( GUIUtility.hotControl == 0
&& e.type == EventType.MouseDown
&& e.button == 0 ) {
bool shift = ( e.modifiers & EventModifiers.Shift ) != 0;
Vector2 mouseGui = e.mousePosition;
int closestIdx = -1;
// Tighter radius for shift-click (precise toggle) than for plain
// click-to-retarget (covers the position handle's pickable center).
float closestDist = shift ? SELECT_DOT_GUI_RADIUS * 2.5f : POSITION_HANDLE_AVOID_PIXELS;
for( int i = 0; i < points.Count; i++ ) {
if( positionEnabledArray != null && positionEnabledArray[i] == false ) continue;
Vector2 gp = HandleUtility.WorldToGUIPoint( GetWorldPt( i ) );
float d = Vector2.Distance( mouseGui, gp );
if( d < closestDist ) {
closestDist = d;
closestIdx = i;
}
}
if( closestIdx >= 0 ) {
if( shift ) {
if( !selectedIndices.Add( closestIdx ) )
selectedIndices.Remove( closestIdx );
e.Use();
} else if( selectedIndices.Count <= 1 ) {
// Skip when multi-select is active so clicking the centroid
// handle doesn't collapse the selection.
selectedIndices.Clear();
selectedIndices.Add( closestIdx );
// Don't Use() — let the position handle take the drag.
}
SceneView.RepaintAll();
}
}
// --- Edge-hover ghost (insert) ---
// Suppressed entirely while a multi-select is active: the centroid handle
// sits at the midpoint of two selected adjacent points, exactly where the
// ghost would render, and it would steal the centroid handle's click.
int hoverEdgeStart = -1;
Vector3 hoverInsertWorld = default;
float hoverInsertT = 0f;
if( hasAddRemoveMode && points.Count >= 2 && selectedIndices.Count <= 1 ) {
Vector2 mouseGui = e.mousePosition;
// Suppress ghost if the mouse is anywhere near a point's position-handle
// territory. Without this, an edge that passes under a handle (or runs
// along one of its axis arrows) lets the ghost get drawn directly under
// the cursor, where Handles.Button steals the click.
bool nearAnyPoint = false;
for( int k = 0; k < points.Count; k++ ) {
if( positionEnabledArray != null && positionEnabledArray[k] == false ) continue;
Vector2 gp = HandleUtility.WorldToGUIPoint( GetWorldPt( k ) );
if( Vector2.Distance( mouseGui, gp ) < POSITION_HANDLE_AVOID_PIXELS ) {
nearAnyPoint = true;
break;
}
}
if( !nearAnyPoint ) {
float bestDist = EDGE_HOVER_PIXEL_THRESHOLD;
int edgeCount = closed ? points.Count : points.Count - 1;
for( int i = 0; i < edgeCount; i++ ) {
int j = ( i + 1 ) % points.Count;
Vector3 wa = GetWorldPt( i );
Vector3 wb = GetWorldPt( j );
Vector2 ga = HandleUtility.WorldToGUIPoint( wa );
Vector2 gb = HandleUtility.WorldToGUIPoint( wb );
Vector2 ab = gb - ga;
float lenSq = ab.sqrMagnitude;
if( lenSq < 0.0001f ) continue;
float t = Mathf.Clamp01( Vector2.Dot( mouseGui - ga, ab ) / lenSq );
Vector2 closestGui = ga + t * ab;
// Skip if closer to either endpoint (those are point selectors)
if( Vector2.Distance( closestGui, ga ) < SELECT_DOT_GUI_RADIUS * 1.5f ) continue;
if( Vector2.Distance( closestGui, gb ) < SELECT_DOT_GUI_RADIUS * 1.5f ) continue;
float dist = Vector2.Distance( mouseGui, closestGui );
if( dist < bestDist ) {
bestDist = dist;
hoverEdgeStart = i;
hoverInsertT = t;
hoverInsertWorld = Vector3.LerpUnclamped( wa, wb, t );
}
}
}
}
// --- Selection dots (per point, click to select / shift-click to toggle) ---
for( int i = 0; i < points.Count; i++ ) {
if( positionEnabledArray != null && positionEnabledArray[i] == false ) continue;
Vector3 ptWorld = GetWorldPt( i );
bool sel = selectedIndices.Contains( i );
float handleSize = HandleUtility.GetHandleSize( ptWorld ) * 0.06f;
Color dotColor = sel ? new Color( 1f, 0.7f, 0.1f ) : new Color( 0.2f, 0.6f, 1f );
using( new Handles.DrawingScope( dotColor ) ) {
if( Handles.Button( ptWorld, Quaternion.identity, handleSize, handleSize * 1.6f, Handles.DotHandleCap ) ) {
bool shift = ( e.modifiers & EventModifiers.Shift ) != 0;
if( shift ) {
if( !selectedIndices.Add( i ) )
selectedIndices.Remove( i );
} else {
selectedIndices.Clear();
selectedIndices.Add( i );
}
SceneView.RepaintAll();
}
}
}
// --- Drag of just-inserted point (ghost MouseDown handed us hotControl) ---
// Process the drag BEFORE the position handles draw so each handle reads
// the freshly-set position on the same frame.
if( insertDragControlID != 0 && GUIUtility.hotControl == insertDragControlID ) {
if( insertDragPointIndex >= 0 && insertDragPointIndex < points.Count ) {
if( e.type == EventType.MouseDrag ) {
Vector3 worldPos;
if( ProjectMouseOntoPlane( tf, e.mousePosition, out worldPos ) ) {
worldPos = SnapWorldIfEnabled( worldPos );
Undo.RecordObject( component, "drag inserted point" );
SetPt( insertDragPointIndex, tf.InverseTransformPoint( worldPos ) );
changed = true;
e.Use();
}
} else if( e.type == EventType.MouseUp && e.button == 0 ) {
GUIUtility.hotControl = 0;
insertDragControlID = 0;
insertDragPointIndex = -1;
e.Use();
}
} else {
// Index went out of range (undo, external edit) — bail safely.
GUIUtility.hotControl = 0;
insertDragControlID = 0;
insertDragPointIndex = -1;
}
}
// --- Movement handles ---
if( selectedIndices.Count > 1 ) {
// Combined handle at centroid of selected
Vector3 centroid = Vector3.zero;
foreach( int idx in selectedIndices ) centroid += GetWorldPt( idx );
centroid /= selectedIndices.Count;
EditorGUI.BeginChangeCheck();
Vector3 newCentroid = Handles.PositionHandle( centroid, handleRotation );
if( EditorGUI.EndChangeCheck() ) {
Vector3 deltaWorld = newCentroid - centroid;
if( deltaWorld.sqrMagnitude > 0f ) {
Undo.RecordObject( component, "move points" );
foreach( int idx in selectedIndices ) {
Vector3 nw = GetWorldPt( idx ) + deltaWorld;
SetPt( idx, tf.InverseTransformPoint( nw ) );
}
changed = true;
}
}
} else {
// Always draw a position handle on every point (when 0 or 1 selected) so the
// handle count — and therefore the ControlIDs Handles.PositionHandle allocates
// — stays stable frame-to-frame. Filtering by selection here caused Unity's
// hot control to map to the wrong index when the count changed, which made a
// drag "grab the last point I was moving."
for( int i = 0; i < points.Count; i++ ) {
if( positionEnabledArray != null && positionEnabledArray[i] == false ) continue;
Vector3 ptWorld = GetWorldPt( i );
EditorGUI.BeginChangeCheck();
Vector3 newPosWorld = Handles.PositionHandle( ptWorld, handleRotation );
if( EditorGUI.EndChangeCheck() ) {
changed = true;
Undo.RecordObject( component, "modify points" );
SetPt( i, tf.InverseTransformPoint( newPosWorld ) );
// Retarget selection to whichever point was actually dragged —
// covers the case where the click lands on an axis arrow tip
// far from the point center, outside the MouseDown radius.
selectedIndices.Clear();
selectedIndices.Add( i );
}
}
}
// --- Edge insert ghost (manual control) ---
// Drawn AFTER the position handles so its conditional ControlID can't
// shift theirs. We don't use Handles.Button here because that only
// fires on click-release; we want MouseDown to insert the point AND
// claim hotControl so the user can drag the new point straight to its
// final position and release it on MouseUp.
if( hoverEdgeStart >= 0 && insertDragControlID == 0 ) {
int ghostControlID = GUIUtility.GetControlID( FocusType.Passive );
float ghostSize = HandleUtility.GetHandleSize( hoverInsertWorld ) * 0.06f;
if( e.type == EventType.Repaint ) {
using( new Handles.DrawingScope( new Color( 0.4f, 1f, 0.4f, 0.8f ) ) )
Handles.SphereHandleCap( ghostControlID, hoverInsertWorld, Quaternion.identity, ghostSize, EventType.Repaint );
} else if( e.type == EventType.MouseDown && e.button == 0 ) {
Vector2 ghostGui = HandleUtility.WorldToGUIPoint( hoverInsertWorld );
if( Vector2.Distance( e.mousePosition, ghostGui ) <= EDGE_HOVER_PIXEL_THRESHOLD ) {
Undo.RecordObject( component, "insert point" );
T newPt = Lerp( points[hoverEdgeStart], points[( hoverEdgeStart + 1 ) % points.Count], hoverInsertT );
int insertAt = hoverEdgeStart + 1;
points.Insert( insertAt, newPt );
// Initial position: the ghost world position, snapped if the
// magnet is on. The drag handler re-snaps each MouseDrag.
Vector3 startWorld = SnapWorldIfEnabled( hoverInsertWorld );
SetPt( insertAt, tf.InverseTransformPoint( startWorld ) );
selectedIndices.Clear();
selectedIndices.Add( insertAt );
insertDragControlID = ghostControlID;
insertDragPointIndex = insertAt;
GUIUtility.hotControl = ghostControlID;
e.Use();
changed = true;
}
}
// Repaint while moving so the ghost tracks the mouse smoothly
if( e.type == EventType.MouseMove )
SceneView.lastActiveSceneView.Repaint();
}
// --- Keyboard shortcuts ---
if( e.type == EventType.KeyDown && hasAddRemoveMode && selectedIndices.Count > 0 ) {
bool ctrl = ( e.modifiers & ( EventModifiers.Control | EventModifiers.Command ) ) != 0;
if( e.keyCode == KeyCode.Delete || e.keyCode == KeyCode.Backspace ) {
int allowedRemovals = Mathf.Max( 0, points.Count - minPoints );
if( allowedRemovals > 0 ) {
Undo.RecordObject( component, "delete points" );
var toRemove = selectedIndices.OrderByDescending( x => x ).Take( allowedRemovals ).ToList();
foreach( int idx in toRemove )
points.RemoveAt( idx );
selectedIndices.Clear();
changed = true;
}
e.Use();
SceneView.RepaintAll();
} else if( ctrl && e.keyCode == KeyCode.D ) {
Undo.RecordObject( component, "duplicate points" );
var sortedAsc = selectedIndices.OrderBy( x => x ).ToList();
// Insert highest-first so earlier indices don't shift
for( int i = sortedAsc.Count - 1; i >= 0; i-- ) {
int idx = sortedAsc[i];
points.Insert( idx + 1, points[idx] );
}
selectedIndices.Clear();
for( int i = 0; i < sortedAsc.Count; i++ )
selectedIndices.Add( sortedAsc[i] + 1 + i );
changed = true;
e.Use();
SceneView.RepaintAll();
} else if( e.keyCode == KeyCode.A && ctrl ) {
selectedIndices.Clear();
for( int i = 0; i < points.Count; i++ )
selectedIndices.Add( i );
e.Use();
SceneView.RepaintAll();
}
}
return changed;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment