Compare commits

...

15 Commits

Author SHA1 Message Date
Aakansha Doshi
ef8bcbe1f8 cache the visible point indexes 2022-09-16 16:57:41 +05:30
Aakansha Doshi
02d5cc4174 Merge remote-tracking branch 'origin/master' into aakansha-hide-close-linear-element-points 2022-09-16 16:53:50 +05:30
Aakansha Doshi
2f4d051ea1 fix specs 2022-09-01 18:26:43 +05:30
Aakansha Doshi
0b0846bd8e fix index 2022-09-01 18:03:49 +05:30
Aakansha Doshi
330b3a0530 hide n-1 point if distance is less than threshold 2022-09-01 17:51:50 +05:30
Aakansha Doshi
cd195374bc don't allow dragging hidden points 2022-09-01 17:30:31 +05:30
Aakansha Doshi
8971d06655 Merge remote-tracking branch 'origin/master' into aakansha-hide-close-linear-element-points 2022-09-01 16:18:52 +05:30
Aakansha Doshi
1e4e37b87f empty 2022-08-26 21:16:51 +05:30
Aakansha Doshi
189a557ed6 update visible point indexes when zooming 2022-08-26 20:49:50 +05:30
Aakansha Doshi
bff2f9178d update visiblePointIndexes when points added 2022-08-26 18:53:15 +05:30
Aakansha Doshi
9b5715623a don't update points unitil deselected 2022-08-26 14:14:01 +05:30
Aakansha Doshi
3487f0ab26 Always show extreme points 2022-08-24 16:48:57 +05:30
Aakansha Doshi
76910828c2 check for 2 pointer arrows 2022-08-24 16:04:17 +05:30
Aakansha Doshi
fde521ef4d fix 2022-08-24 15:58:43 +05:30
Aakansha Doshi
bcb45f7cf6 fix: hide points outside linear element editor when close enough 2022-08-24 15:49:04 +05:30
6 changed files with 150 additions and 39 deletions

View File

@ -33,6 +33,9 @@ export const actionFinalize = register({
endBindingElement, endBindingElement,
); );
} }
const selectedLinearElement = appState.selectedLinearElement
? new LinearElementEditor(element, scene, appState)
: null;
return { return {
elements: elements:
element.points.length < 2 || isInvisiblySmallElement(element) element.points.length < 2 || isInvisiblySmallElement(element)
@ -42,6 +45,7 @@ export const actionFinalize = register({
...appState, ...appState,
cursorButton: "up", cursorButton: "up",
editingLinearElement: null, editingLinearElement: null,
selectedLinearElement,
}, },
commitToHistory: true, commitToHistory: true,
}; };
@ -184,7 +188,7 @@ export const actionFinalize = register({
// To select the linear element when user has finished mutipoint editing // To select the linear element when user has finished mutipoint editing
selectedLinearElement: selectedLinearElement:
multiPointElement && isLinearElement(multiPointElement) multiPointElement && isLinearElement(multiPointElement)
? new LinearElementEditor(multiPointElement, scene) ? new LinearElementEditor(multiPointElement, scene, appState)
: appState.selectedLinearElement, : appState.selectedLinearElement,
pendingImageElementId: null, pendingImageElementId: null,
}, },

View File

@ -35,7 +35,7 @@ export const actionSelectAll = register({
// single linear element selected // single linear element selected
Object.keys(selectedElementIds).length === 1 && Object.keys(selectedElementIds).length === 1 &&
isLinearElement(elements[0]) isLinearElement(elements[0])
? new LinearElementEditor(elements[0], app.scene) ? new LinearElementEditor(elements[0], app.scene, appState)
: null, : null,
editingGroupId: null, editingGroupId: null,
selectedElementIds, selectedElementIds,

View File

@ -1907,6 +1907,8 @@ class App extends React.Component<AppProps, AppState> {
editingLinearElement: new LinearElementEditor( editingLinearElement: new LinearElementEditor(
selectedElements[0], selectedElements[0],
this.scene, this.scene,
this.state,
true,
), ),
}); });
} }
@ -2485,6 +2487,8 @@ class App extends React.Component<AppProps, AppState> {
editingLinearElement: new LinearElementEditor( editingLinearElement: new LinearElementEditor(
selectedElements[0], selectedElements[0],
this.scene, this.scene,
this.state,
true,
), ),
}); });
} }
@ -3071,7 +3075,7 @@ class App extends React.Component<AppProps, AppState> {
]) ])
) { ) {
hoverPointIndex = LinearElementEditor.getPointIndexUnderCursor( hoverPointIndex = LinearElementEditor.getPointIndexUnderCursor(
element, this.state.selectedLinearElement,
this.state.zoom, this.state.zoom,
scenePointerX, scenePointerX,
scenePointerY, scenePointerY,
@ -4488,6 +4492,7 @@ class App extends React.Component<AppProps, AppState> {
? new LinearElementEditor( ? new LinearElementEditor(
elementsWithinSelection[0], elementsWithinSelection[0],
this.scene, this.scene,
this.state,
) )
: null, : null,
}, },
@ -4752,6 +4757,7 @@ class App extends React.Component<AppProps, AppState> {
selectedLinearElement: new LinearElementEditor( selectedLinearElement: new LinearElementEditor(
draggingElement, draggingElement,
this.scene, this.scene,
this.state,
), ),
})); }));
} else { } else {
@ -4819,6 +4825,7 @@ class App extends React.Component<AppProps, AppState> {
selectedLinearElement: new LinearElementEditor( selectedLinearElement: new LinearElementEditor(
hitElement, hitElement,
this.scene, this.scene,
this.state,
), ),
}); });
} }
@ -4921,6 +4928,7 @@ class App extends React.Component<AppProps, AppState> {
? new LinearElementEditor( ? new LinearElementEditor(
newSelectedElements[0], newSelectedElements[0],
this.scene, this.scene,
this.state,
) )
: prevState.selectedLinearElement, : prevState.selectedLinearElement,
}, },
@ -4949,7 +4957,11 @@ class App extends React.Component<AppProps, AppState> {
// Don't set `selectedLinearElement` if its same as the hitElement, this is mainly to prevent resetting the `hoverPointIndex` to -1. // Don't set `selectedLinearElement` if its same as the hitElement, this is mainly to prevent resetting the `hoverPointIndex` to -1.
// Future we should update the API to take care of setting the correct `hoverPointIndex` when initialized // Future we should update the API to take care of setting the correct `hoverPointIndex` when initialized
prevState.selectedLinearElement?.elementId !== hitElement.id prevState.selectedLinearElement?.elementId !== hitElement.id
? new LinearElementEditor(hitElement, this.scene) ? new LinearElementEditor(
hitElement,
this.scene,
this.state,
)
: prevState.selectedLinearElement, : prevState.selectedLinearElement,
}, },
this.scene.getNonDeletedElements(), this.scene.getNonDeletedElements(),
@ -5709,7 +5721,7 @@ class App extends React.Component<AppProps, AppState> {
...this.state, ...this.state,
selectedElementIds: { [element.id]: true }, selectedElementIds: { [element.id]: true },
selectedLinearElement: isLinearElement(element) selectedLinearElement: isLinearElement(element)
? new LinearElementEditor(element, this.scene) ? new LinearElementEditor(element, this.scene, this.state)
: null, : null,
}, },
this.scene.getNonDeletedElements(), this.scene.getNonDeletedElements(),

View File

@ -40,6 +40,11 @@ const editorMidPointsCache: {
zoom: number | null; zoom: number | null;
} = { version: null, points: [], zoom: null }; } = { version: null, points: [], zoom: null };
const visiblePointIndexesCache: {
points: number[];
zoom: number | null;
isEditingLinearElement: boolean;
} = { points: [], zoom: null, isEditingLinearElement: false };
export class LinearElementEditor { export class LinearElementEditor {
public readonly elementId: ExcalidrawElement["id"] & { public readonly elementId: ExcalidrawElement["id"] & {
_brand: "excalidrawLinearElementId"; _brand: "excalidrawLinearElementId";
@ -65,7 +70,12 @@ export class LinearElementEditor {
public readonly hoverPointIndex: number; public readonly hoverPointIndex: number;
public readonly segmentMidPointHoveredCoords: Point | null; public readonly segmentMidPointHoveredCoords: Point | null;
constructor(element: NonDeleted<ExcalidrawLinearElement>, scene: Scene) { constructor(
element: NonDeleted<ExcalidrawLinearElement>,
scene: Scene,
appState: AppState,
editingLinearElement = false,
) {
this.elementId = element.id as string & { this.elementId = element.id as string & {
_brand: "excalidrawLinearElementId"; _brand: "excalidrawLinearElementId";
}; };
@ -433,7 +443,7 @@ export class LinearElementEditor {
return null; return null;
} }
const clickedPointIndex = LinearElementEditor.getPointIndexUnderCursor( const clickedPointIndex = LinearElementEditor.getPointIndexUnderCursor(
element, appState.selectedLinearElement,
appState.zoom, appState.zoom,
scenePointer.x, scenePointer.x,
scenePointer.y, scenePointer.y,
@ -560,6 +570,59 @@ export class LinearElementEditor {
return -1; return -1;
} }
static getVisiblePointIndexes(
element: NonDeleted<ExcalidrawLinearElement>,
appState: AppState,
): typeof visiblePointIndexesCache["points"] {
const isEditingLinearElement = !!appState.editingLinearElement;
if (appState.editingLinearElement) {
// So that when we exit the editor the points are calculated again
visiblePointIndexesCache.isEditingLinearElement = true;
return element.points.map((_, index) => index);
}
if (
visiblePointIndexesCache.points &&
visiblePointIndexesCache.zoom === appState.zoom.value &&
isEditingLinearElement === visiblePointIndexesCache.isEditingLinearElement
) {
return visiblePointIndexesCache.points;
}
LinearElementEditor.updateVisiblePointIndexesCache(element, appState);
return visiblePointIndexesCache.points;
}
static updateVisiblePointIndexesCache(
element: NonDeleted<ExcalidrawLinearElement>,
appState: AppState,
) {
const visiblePointIndexes: number[] = [];
let previousPoint: Point | null = null;
element.points.forEach((point, index) => {
let distance = Infinity;
if (previousPoint) {
distance =
distance2d(point[0], point[1], previousPoint[0], previousPoint[1]) *
appState.zoom.value;
}
const isExtremePoint = index === 0 || index === element.points.length - 1;
const threshold = 2 * LinearElementEditor.POINT_HANDLE_SIZE;
if (isExtremePoint || distance >= threshold) {
// hide n-1 point if distance is less than threshold
if (isExtremePoint && distance < threshold) {
visiblePointIndexes.pop();
}
visiblePointIndexes.push(index);
previousPoint = point;
}
});
visiblePointIndexesCache.points = visiblePointIndexes;
visiblePointIndexesCache.zoom = appState.zoom.value;
visiblePointIndexesCache.isEditingLinearElement =
!!appState.editingLinearElement;
}
static handlePointerDown( static handlePointerDown(
event: React.PointerEvent<HTMLCanvasElement>, event: React.PointerEvent<HTMLCanvasElement>,
appState: AppState, appState: AppState,
@ -617,15 +680,6 @@ export class LinearElementEditor {
ret.didAddPoint = true; ret.didAddPoint = true;
ret.isMidPoint = true; ret.isMidPoint = true;
ret.linearElementEditor = {
...linearElementEditor,
selectedPointsIndices: element.points[1],
pointerDownState: {
prevSelectedPointsIndices: linearElementEditor.selectedPointsIndices,
lastClickedPoint: -1,
},
lastUncommittedPoint: null,
};
} }
if (event.altKey && appState.editingLinearElement) { if (event.altKey && appState.editingLinearElement) {
if (linearElementEditor.lastUncommittedPoint == null) { if (linearElementEditor.lastUncommittedPoint == null) {
@ -662,7 +716,7 @@ export class LinearElementEditor {
} }
const clickedPointIndex = LinearElementEditor.getPointIndexUnderCursor( const clickedPointIndex = LinearElementEditor.getPointIndexUnderCursor(
element, appState.selectedLinearElement,
appState.zoom, appState.zoom,
scenePointer.x, scenePointer.x,
scenePointer.y, scenePointer.y,
@ -725,7 +779,11 @@ export class LinearElementEditor {
} }
: { x: 0, y: 0 }, : { x: 0, y: 0 },
}; };
if (ret.didAddPoint) {
ret.linearElementEditor = {
...ret.linearElementEditor,
};
}
return ret; return ret;
} }
@ -873,25 +931,37 @@ export class LinearElementEditor {
} }
static getPointIndexUnderCursor( static getPointIndexUnderCursor(
element: NonDeleted<ExcalidrawLinearElement>, linearElementEditor: LinearElementEditor | null,
zoom: AppState["zoom"], zoom: AppState["zoom"],
x: number, x: number,
y: number, y: number,
) { ) {
if (!linearElementEditor) {
return -1;
}
const element = LinearElementEditor.getElement(
linearElementEditor.elementId,
);
if (!element) {
return -1;
}
const pointHandles = const pointHandles =
LinearElementEditor.getPointsGlobalCoordinates(element); LinearElementEditor.getPointsGlobalCoordinates(element);
let idx = pointHandles.length; let counter = visiblePointIndexesCache.points.length;
// loop from right to left because points on the right are rendered over // loop from right to left because points on the right are rendered over
// points on the left, thus should take precedence when clicking, if they // points on the left, thus should take precedence when clicking, if they
// overlap // overlap
while (--idx > -1) { while (--counter >= 0) {
const point = pointHandles[idx]; const index = visiblePointIndexesCache.points[counter];
const point = pointHandles[index];
if ( if (
distance2d(x, y, point[0], point[1]) * zoom.value < distance2d(x, y, point[0], point[1]) * zoom.value <
// +1px to account for outline stroke // +1px to account for outline stroke
LinearElementEditor.POINT_HANDLE_SIZE + 1 LinearElementEditor.POINT_HANDLE_SIZE + 1
) { ) {
return idx; return index;
} }
} }
return -1; return -1;

View File

@ -159,13 +159,15 @@ const strokeGrid = (
const renderSingleLinearPoint = ( const renderSingleLinearPoint = (
context: CanvasRenderingContext2D, context: CanvasRenderingContext2D,
appState: AppState,
renderConfig: RenderConfig, renderConfig: RenderConfig,
point: Point, point: Point,
radius: number, radius: number,
isSelected: boolean, isSelected: boolean,
isPhantomPoint = false, isPhantomPoint = false,
) => { ) => {
if (!point) {
return;
}
context.strokeStyle = "#5e5ad8"; context.strokeStyle = "#5e5ad8";
context.setLineDash([]); context.setLineDash([]);
context.fillStyle = "rgba(255, 255, 255, 0.9)"; context.fillStyle = "rgba(255, 255, 255, 0.9)";
@ -202,18 +204,16 @@ const renderLinearPointHandles = (
const radius = appState.editingLinearElement const radius = appState.editingLinearElement
? POINT_HANDLE_SIZE ? POINT_HANDLE_SIZE
: POINT_HANDLE_SIZE / 2; : POINT_HANDLE_SIZE / 2;
points.forEach((point, idx) => {
const isSelected =
!!appState.editingLinearElement?.selectedPointsIndices?.includes(idx);
renderSingleLinearPoint( const visiblePointIndexes = LinearElementEditor.getVisiblePointIndexes(
context, element,
appState, appState,
renderConfig, );
point, visiblePointIndexes.forEach((index) => {
radius, const isSelected =
isSelected, !!appState.editingLinearElement?.selectedPointsIndices?.includes(index);
); const point = points[index];
renderSingleLinearPoint(context, renderConfig, point, radius, isSelected);
}); });
//Rendering segment mid points //Rendering segment mid points
@ -237,7 +237,6 @@ const renderLinearPointHandles = (
if (appState.editingLinearElement) { if (appState.editingLinearElement) {
renderSingleLinearPoint( renderSingleLinearPoint(
context, context,
appState,
renderConfig, renderConfig,
segmentMidPoint, segmentMidPoint,
radius, radius,
@ -248,7 +247,7 @@ const renderLinearPointHandles = (
highlightPoint(segmentMidPoint, context, renderConfig); highlightPoint(segmentMidPoint, context, renderConfig);
renderSingleLinearPoint( renderSingleLinearPoint(
context, context,
appState,
renderConfig, renderConfig,
segmentMidPoint, segmentMidPoint,
radius, radius,
@ -258,7 +257,6 @@ const renderLinearPointHandles = (
} else if (appState.editingLinearElement || points.length === 2) { } else if (appState.editingLinearElement || points.length === 2) {
renderSingleLinearPoint( renderSingleLinearPoint(
context, context,
appState,
renderConfig, renderConfig,
segmentMidPoint, segmentMidPoint,
POINT_HANDLE_SIZE / 2, POINT_HANDLE_SIZE / 2,
@ -450,7 +448,22 @@ export const _renderScene = ({
appState.selectedLinearElement && appState.selectedLinearElement &&
appState.selectedLinearElement.hoverPointIndex >= 0 appState.selectedLinearElement.hoverPointIndex >= 0
) { ) {
renderLinearElementPointHighlight(context, appState, renderConfig); const element = LinearElementEditor.getElement(
appState.selectedLinearElement.elementId,
);
if (element) {
const visiblePointIndexes = LinearElementEditor.getVisiblePointIndexes(
element,
appState,
);
if (
visiblePointIndexes.includes(
appState.selectedLinearElement.hoverPointIndex,
)
) {
renderLinearElementPointHighlight(context, appState, renderConfig);
}
}
} }
// Paint selected elements // Paint selected elements
if ( if (

View File

@ -10993,6 +10993,9 @@ Object {
"segmentMidPointHoveredCoords": null, "segmentMidPointHoveredCoords": null,
"selectedPointsIndices": null, "selectedPointsIndices": null,
"startBindingElement": "keep", "startBindingElement": "keep",
"visiblePointIndexes": Array [
1,
],
}, },
"selectionElement": null, "selectionElement": null,
"shouldCacheIgnoreZoom": false, "shouldCacheIgnoreZoom": false,
@ -11219,6 +11222,9 @@ Object {
"segmentMidPointHoveredCoords": null, "segmentMidPointHoveredCoords": null,
"selectedPointsIndices": null, "selectedPointsIndices": null,
"startBindingElement": "keep", "startBindingElement": "keep",
"visiblePointIndexes": Array [
1,
],
}, },
"selectionElement": null, "selectionElement": null,
"shouldCacheIgnoreZoom": false, "shouldCacheIgnoreZoom": false,
@ -11672,6 +11678,9 @@ Object {
"segmentMidPointHoveredCoords": null, "segmentMidPointHoveredCoords": null,
"selectedPointsIndices": null, "selectedPointsIndices": null,
"startBindingElement": "keep", "startBindingElement": "keep",
"visiblePointIndexes": Array [
1,
],
}, },
"selectionElement": null, "selectionElement": null,
"shouldCacheIgnoreZoom": false, "shouldCacheIgnoreZoom": false,
@ -12077,6 +12086,9 @@ Object {
"segmentMidPointHoveredCoords": null, "segmentMidPointHoveredCoords": null,
"selectedPointsIndices": null, "selectedPointsIndices": null,
"startBindingElement": "keep", "startBindingElement": "keep",
"visiblePointIndexes": Array [
1,
],
}, },
"selectionElement": null, "selectionElement": null,
"shouldCacheIgnoreZoom": false, "shouldCacheIgnoreZoom": false,