Merge remote-tracking branch 'origin/stickies-rc' into lu/frame-kickout-rotation

lu/frame-kickout-rotation
Lu[ke] Wilson 2024-04-09 09:37:14 +01:00
commit bcd412bedb
31 zmienionych plików z 266 dodań i 138 usunięć

Wyświetl plik

@ -36,7 +36,8 @@ export function CustomRenderer() {
const theme = getDefaultColorTheme({ isDarkMode: editor.user.getIsDarkMode() })
const currentPageId = editor.getCurrentPageId()
for (const { shape, maskedPageBounds, opacity } of renderingShapes) {
for (const { shape, opacity } of renderingShapes) {
const maskedPageBounds = editor.getShapeMaskedPageBounds(shape)
if (!maskedPageBounds) continue
ctx.save()

Wyświetl plik

@ -22,9 +22,11 @@ export function useChangedShapesReactor(
if (!beforeInfo) {
continue
} else {
if (afterInfo.isCulled && !beforeInfo.isCulled) {
const isAfterCulled = editor.isShapeCulled(afterInfo.id)
const isBeforeCulled = editor.isShapeCulled(beforeInfo.id)
if (isAfterCulled && !isBeforeCulled) {
culled.push(afterInfo.shape)
} else if (!afterInfo.isCulled && beforeInfo.isCulled) {
} else if (!isAfterCulled && isBeforeCulled) {
restored.push(afterInfo.shape)
}
beforeToVisit.delete(beforeInfo)

Wyświetl plik

@ -514,7 +514,7 @@ export function degreesToRadians(d: number): number;
export const DOUBLE_CLICK_DURATION = 450;
// @internal (undocumented)
export const DRAG_DISTANCE = 4;
export const DRAG_DISTANCE = 16;
// @public (undocumented)
export const EASINGS: {
@ -721,8 +721,6 @@ export class Editor extends EventEmitter<TLEventMap> {
index: number;
backgroundIndex: number;
opacity: number;
isCulled: boolean;
maskedPageBounds: Box | undefined;
}[];
getSelectedShapeAtPoint(point: VecLike): TLShape | undefined;
getSelectedShapeIds(): TLShapeId[];
@ -783,8 +781,6 @@ export class Editor extends EventEmitter<TLEventMap> {
index: number;
backgroundIndex: number;
opacity: number;
isCulled: boolean;
maskedPageBounds: Box | undefined;
}[];
getViewportPageBounds(): Box;
getViewportPageCenter(): Vec;
@ -821,6 +817,7 @@ export class Editor extends EventEmitter<TLEventMap> {
margin?: number | undefined;
hitInside?: boolean | undefined;
}): boolean;
isShapeCulled(shape: TLShape | TLShapeId): boolean;
isShapeInPage(shape: TLShape | TLShapeId, pageId?: TLPageId): boolean;
isShapeOfType<T extends TLUnknownShape>(shape: TLUnknownShape, type: T['type']): shape is T;
// (undocumented)

Wyświetl plik

@ -11991,16 +11991,7 @@
},
{
"kind": "Content",
"text": ">;\n index: number;\n backgroundIndex: number;\n opacity: number;\n isCulled: boolean;\n maskedPageBounds: "
},
{
"kind": "Reference",
"text": "Box",
"canonicalReference": "@tldraw/editor!Box:class"
},
{
"kind": "Content",
"text": " | undefined;\n }[]"
"text": ">;\n index: number;\n backgroundIndex: number;\n opacity: number;\n }[]"
},
{
"kind": "Content",
@ -12010,7 +12001,7 @@
"isStatic": false,
"returnTypeTokenRange": {
"startIndex": 1,
"endIndex": 12
"endIndex": 10
},
"releaseTag": "Public",
"isProtected": false,
@ -14801,6 +14792,64 @@
"isAbstract": false,
"name": "isPointInShape"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!Editor#isShapeCulled:member(1)",
"docComment": "/**\n * Get whether the shape is culled or not.\n *\n * @param shape - The shape (or shape id) to get the culled info for.\n *\n * @example\n * ```ts\n * editor.isShapeCulled(myShape)\n * editor.isShapeCulled(myShapeId)\n * ```\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "isShapeCulled(shape: "
},
{
"kind": "Reference",
"text": "TLShape",
"canonicalReference": "@tldraw/tlschema!TLShape:type"
},
{
"kind": "Content",
"text": " | "
},
{
"kind": "Reference",
"text": "TLShapeId",
"canonicalReference": "@tldraw/tlschema!TLShapeId:type"
},
{
"kind": "Content",
"text": "): "
},
{
"kind": "Content",
"text": "boolean"
},
{
"kind": "Content",
"text": ";"
}
],
"isStatic": false,
"returnTypeTokenRange": {
"startIndex": 5,
"endIndex": 6
},
"releaseTag": "Public",
"isProtected": false,
"overloadIndex": 1,
"parameters": [
{
"parameterName": "shape",
"parameterTypeTokenRange": {
"startIndex": 1,
"endIndex": 4
},
"isOptional": false
}
],
"isOptional": false,
"isAbstract": false,
"name": "isShapeCulled"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!Editor#isShapeInPage:member(1)",

Wyświetl plik

@ -1102,9 +1102,12 @@ input,
}
/* This part of the rule helps preserve the occlusion rules for the shapes so we
* don't click on shapes that are behind other shapes.
* One extra nuance is arrows which have weird geometry and just gets in the way.
* One extra nuance is we don't use this behavior for:
* - arrows which have weird geometry and just gets in the way.
* - draw shapes, because it feels restrictive to have them be 'in the way' of clicking on a textfield
*/
.tl-canvas[data-iseditinganything='true'] .tl-shape:not([data-shape-type='arrow']) {
.tl-canvas[data-iseditinganything='true']
.tl-shape:not([data-shape-type='arrow']):not([data-shape-type='draw']) {
pointer-events: all;
}
/* But, re-disable the pointer-events rule for the svg container. */

Wyświetl plik

@ -112,8 +112,9 @@ export function CulledShapes() {
const shapeVertices = computed('shape vertices', function calculateCulledShapeVertices() {
const results: number[] = []
for (const { isCulled, maskedPageBounds } of editor.getRenderingShapes()) {
if (isCulled && maskedPageBounds) {
for (const { id } of editor.getUnorderedRenderingShapes(true)) {
const maskedPageBounds = editor.getShapeMaskedPageBounds(id)
if (editor.isShapeCulled(id) && maskedPageBounds) {
results.push(
// triangle 1
maskedPageBounds.minX,

Wyświetl plik

@ -1,6 +1,6 @@
import { useQuickReactor, useStateTracking } from '@tldraw/state'
import { TLShape, TLShapeId } from '@tldraw/tlschema'
import { memo, useCallback, useLayoutEffect, useRef } from 'react'
import { memo, useCallback, useRef } from 'react'
import { ShapeUtil } from '../editor/shapes/ShapeUtil'
import { useEditor } from '../hooks/useEditor'
import { useEditorComponents } from '../hooks/useEditorComponents'
@ -26,7 +26,6 @@ export const Shape = memo(function Shape({
index,
backgroundIndex,
opacity,
isCulled,
dprMultiple,
}: {
id: TLShapeId
@ -35,7 +34,6 @@ export const Shape = memo(function Shape({
index: number
backgroundIndex: number
opacity: number
isCulled: boolean
dprMultiple: number
}) {
const editor = useEditor()
@ -120,13 +118,18 @@ export const Shape = memo(function Shape({
[opacity, index, backgroundIndex]
)
useLayoutEffect(() => {
const container = containerRef.current
const bgContainer = bgContainerRef.current
setStyleProperty(container, 'display', isCulled ? 'none' : 'block')
setStyleProperty(bgContainer, 'display', isCulled ? 'none' : 'block')
}, [isCulled])
useQuickReactor(
'set display',
() => {
const shape = editor.getShape(id)
if (!shape) return // probably the shape was just deleted
const isCulled = editor.isShapeCulled(shape)
setStyleProperty(containerRef.current, 'display', isCulled ? 'none' : 'block')
setStyleProperty(bgContainerRef.current, 'display', isCulled ? 'none' : 'block')
},
[editor]
)
const annotateError = useCallback(
(error: any) => editor.annotateError(error, { origin: 'shape', willCrashApp: false }),
[editor]

Wyświetl plik

@ -403,6 +403,7 @@ function SelectedIdIndicators() {
'select.idle',
'select.brushing',
'select.scribble_brushing',
'select.editing_shape',
'select.pointing_shape',
'select.pointing_selection',
'select.pointing_handle'

Wyświetl plik

@ -34,10 +34,10 @@ export const DOUBLE_CLICK_DURATION = 450
export const MULTI_CLICK_DURATION = 200
/** @internal */
export const COARSE_DRAG_DISTANCE = 6
export const COARSE_DRAG_DISTANCE = 36 // 6 squared
/** @internal */
export const DRAG_DISTANCE = 4
export const DRAG_DISTANCE = 16 // 4 squared
/** @internal */
export const SVG_PADDING = 32

Wyświetl plik

@ -3124,48 +3124,26 @@ export class Editor extends EventEmitter<TLEventMap> {
index: number
backgroundIndex: number
opacity: number
isCulled: boolean
maskedPageBounds: Box | undefined
}[] = []
let nextIndex = MAX_SHAPES_PER_PAGE * 2
let nextBackgroundIndex = MAX_SHAPES_PER_PAGE
// We only really need these if we're using editor state, but that's ok
const editingShapeId = this.getEditingShapeId()
const selectedShapeIds = this.getSelectedShapeIds()
const erasingShapeIds = this.getErasingShapeIds()
const renderingBoundsExpanded = this.getRenderingBoundsExpanded()
// If renderingBoundsMargin is set to Infinity, then we won't cull offscreen shapes
const isCullingOffScreenShapes = Number.isFinite(this.renderingBoundsMargin)
const addShapeById = (id: TLShapeId, opacity: number, isAncestorErasing: boolean) => {
const shape = this.getShape(id)
if (!shape) return
opacity *= shape.opacity
let isCulled = false
let isShapeErasing = false
const util = this.getShapeUtil(shape)
const maskedPageBounds = this.getShapeMaskedPageBounds(id)
if (useEditorState) {
isShapeErasing = !isAncestorErasing && erasingShapeIds.includes(id)
if (isShapeErasing) {
opacity *= 0.32
}
isCulled =
isCullingOffScreenShapes &&
// never cull editingg shapes
editingShapeId !== id &&
// if the shape is fully outside of its parent's clipping bounds...
(maskedPageBounds === undefined ||
// ...or if the shape is outside of the expanded viewport bounds...
(!renderingBoundsExpanded.includes(maskedPageBounds) &&
// ...and if it's not selected... then cull it
!selectedShapeIds.includes(id)))
}
renderingShapes.push({
@ -3175,8 +3153,6 @@ export class Editor extends EventEmitter<TLEventMap> {
index: nextIndex,
backgroundIndex: nextBackgroundIndex,
opacity,
isCulled,
maskedPageBounds,
})
nextIndex += 1
@ -4270,6 +4246,51 @@ export class Editor extends EventEmitter<TLEventMap> {
return this.isShapeOrAncestorLocked(this.getShapeParent(shape))
}
@computed
private _getShapeCullingInfoCache(): ComputedCache<boolean, TLShape> {
return this.store.createComputedCache(
'shapeCullingInfo',
({ id }) => {
// We don't cull shapes that are being edited
if (this.getEditingShapeId() === id) return false
const maskedPageBounds = this.getShapeMaskedPageBounds(id)
// if the shape is fully outside of its parent's clipping bounds...
if (maskedPageBounds === undefined) return true
// We don't cull selected shapes
if (this.getSelectedShapeIds().includes(id)) return false
const renderingBoundsExpanded = this.getRenderingBoundsExpanded()
// the shape is outside of the expanded viewport bounds...
return !renderingBoundsExpanded.includes(maskedPageBounds)
},
(a, b) => this.getShapeMaskedPageBounds(a) === this.getShapeMaskedPageBounds(b)
)
}
/**
* Get whether the shape is culled or not.
*
* @example
* ```ts
* editor.isShapeCulled(myShape)
* editor.isShapeCulled(myShapeId)
* ```
*
* @param shape - The shape (or shape id) to get the culled info for.
*
* @public
*/
isShapeCulled(shape: TLShape | TLShapeId): boolean {
// If renderingBoundsMargin is set to Infinity, then we won't cull offscreen shapes
const isCullingOffScreenShapes = Number.isFinite(this.renderingBoundsMargin)
if (!isCullingOffScreenShapes) return false
const id = typeof shape === 'string' ? shape : shape.id
return this._getShapeCullingInfoCache().get(id)! as boolean
}
/**
* The bounds of the current page (the common bounds of all of the shapes on the page).
*
@ -4641,8 +4662,8 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
@computed getCurrentPageRenderingShapesSorted(): TLShape[] {
return this.getRenderingShapes()
.filter(({ isCulled }) => !isCulled)
return this.getUnorderedRenderingShapes(true)
.filter(({ id }) => !this.isShapeCulled(id))
.sort((a, b) => a.index - b.index)
.map(({ shape }) => shape)
}
@ -8579,7 +8600,7 @@ export class Editor extends EventEmitter<TLEventMap> {
if (
!inputs.isDragging &&
inputs.isPointing &&
originPagePoint.dist(currentPagePoint) >
Vec.Dist2(originPagePoint, currentPagePoint) >
(this.getInstanceState().isCoarsePointer ? COARSE_DRAG_DISTANCE : DRAG_DISTANCE) /
this.getZoomLevel()
) {
@ -8669,7 +8690,7 @@ export class Editor extends EventEmitter<TLEventMap> {
if (
!inputs.isDragging &&
inputs.isPointing &&
originPagePoint.dist(currentPagePoint) >
Vec.Dist2(originPagePoint, currentPagePoint) >
(this.getInstanceState().isCoarsePointer ? COARSE_DRAG_DISTANCE : DRAG_DISTANCE) /
this.getZoomLevel()
) {

Wyświetl plik

@ -38,7 +38,8 @@ export async function getSvgJsx(
if (opts.bounds) {
bbox = opts.bounds
} else {
for (const { maskedPageBounds } of renderingShapes) {
for (const { id } of renderingShapes) {
const maskedPageBounds = editor.getShapeMaskedPageBounds(id)
if (!maskedPageBounds) continue
if (bbox) {
bbox.union(maskedPageBounds)

Wyświetl plik

@ -227,7 +227,7 @@ export class ClickManager {
if (
this._clickState !== 'idle' &&
this._clickScreenPoint &&
this._clickScreenPoint.dist(this.editor.inputs.currentScreenPoint) >
Vec.Dist2(this._clickScreenPoint, this.editor.inputs.currentScreenPoint) >
(this.editor.getInstanceState().isCoarsePointer ? COARSE_DRAG_DISTANCE : DRAG_DISTANCE)
) {
this.cancelDoubleClickTimeout()

Wyświetl plik

@ -309,18 +309,20 @@ export class Vec {
return new Vec(A.y, -A.x)
}
static Dist2(A: VecLike, B: VecLike): number {
return (A.x - B.x) ** 2 + (A.y - B.y) ** 2
}
static Abs(A: VecLike): Vec {
return new Vec(Math.abs(A.x), Math.abs(A.y))
}
// Get the distance between two points.
static Dist(A: VecLike, B: VecLike): number {
return Math.hypot(A.y - B.y, A.x - B.x)
}
// Get the squared distance between two points. This is faster to calculate (no square root) so useful for "minimum distance" checks where the actual measurement does not matter.
static Dist2(A: VecLike, B: VecLike): number {
return (A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y)
}
/**
* Dot product of two vectors which is used to calculate the angle between them.
*/

Wyświetl plik

@ -52,15 +52,16 @@ export class Arc2d extends Geometry2d {
// Get the point (P) on the arc, then pick the nearest of A, B, and P
const P = _center.clone().add(point.clone().sub(_center).uni().mul(radius))
let distance = Infinity
let nearest: Vec | undefined
for (const pt of [A, B, P]) {
if (point.dist(pt) < distance) {
nearest = pt
distance = point.dist(pt)
let dist = Infinity
let d: number
for (const p of [A, B, P]) {
d = Vec.Dist2(point, p)
if (d < dist) {
nearest = p
dist = d
}
}
if (!nearest) throw Error('nearest point not found')
return nearest
}

Wyświetl plik

@ -55,9 +55,11 @@ export class CubicBezier2d extends Polyline2d {
nearestPoint(A: Vec): Vec {
let nearest: Vec | undefined
let dist = Infinity
let d: number
let p: Vec
for (const edge of this.segments) {
const p = edge.nearestPoint(A)
const d = p.dist(A)
p = edge.nearestPoint(A)
d = Vec.Dist2(p, A)
if (d < dist) {
nearest = p
dist = d

Wyświetl plik

@ -67,15 +67,16 @@ export class CubicSpline2d extends Geometry2d {
nearestPoint(A: Vec): Vec {
let nearest: Vec | undefined
let dist = Infinity
let d: number
let p: Vec
for (const segment of this.segments) {
const p = segment.nearestPoint(A)
const d = p.dist(A)
p = segment.nearestPoint(A)
d = Vec.Dist2(p, A)
if (d < dist) {
nearest = p
dist = d
}
}
if (!nearest) throw Error('nearest point not found')
return nearest
}

Wyświetl plik

@ -76,15 +76,16 @@ export class Ellipse2d extends Geometry2d {
nearestPoint(A: Vec): Vec {
let nearest: Vec | undefined
let dist = Infinity
let d: number
let p: Vec
for (const edge of this.edges) {
const p = edge.nearestPoint(A)
const d = p.dist(A)
p = edge.nearestPoint(A)
d = Vec.Dist2(p, A)
if (d < dist) {
nearest = p
dist = d
}
}
if (!nearest) throw Error('nearest point not found')
return nearest
}

Wyświetl plik

@ -55,14 +55,17 @@ export abstract class Geometry2d {
}
nearestPointOnLineSegment(A: Vec, B: Vec): Vec {
let distance = Infinity
const { vertices } = this
let nearest: Vec | undefined
for (let i = 0; i < this.vertices.length; i++) {
const point = this.vertices[i]
const d = Vec.DistanceToLineSegment(A, B, point)
if (d < distance) {
distance = d
nearest = point
let dist = Infinity
let d: number
let p: Vec
for (let i = 0; i < vertices.length; i++) {
p = vertices[i]
d = Vec.DistanceToLineSegment(A, B, p)
if (d < dist) {
dist = d
nearest = p
}
}
if (!nearest) throw Error('nearest point not found')

Wyświetl plik

@ -30,8 +30,8 @@ export class Group2d extends Geometry2d {
}
override nearestPoint(point: Vec): Vec {
let d = Infinity
let p: Vec | undefined
let dist = Infinity
let nearest: Vec | undefined
const { children } = this
@ -39,16 +39,18 @@ export class Group2d extends Geometry2d {
throw Error('no children')
}
let p: Vec
let d: number
for (const child of children) {
const nearest = child.nearestPoint(point)
const dist = nearest.dist(point)
if (dist < d) {
d = dist
p = nearest
p = child.nearestPoint(point)
d = Vec.Dist2(p, point)
if (d < dist) {
dist = d
nearest = p
}
}
if (!p) throw Error('nearest point not found')
return p
if (!nearest) throw Error('nearest point not found')
return nearest
}
override distanceToPoint(point: Vec, hitInside = false) {

Wyświetl plik

@ -51,18 +51,17 @@ export class Polyline2d extends Geometry2d {
const { segments } = this
let nearest = this.points[0]
let dist = Infinity
let p: Vec // current point on segment
let d: number // distance from A to p
for (let i = 0; i < segments.length; i++) {
p = segments[i].nearestPoint(A)
d = p.dist(A)
d = Vec.Dist2(p, A)
if (d < dist) {
nearest = p
dist = d
}
}
if (!nearest) throw Error('nearest point not found')
return nearest
}

Wyświetl plik

@ -4231,7 +4231,7 @@
{
"kind": "Property",
"canonicalReference": "@tldraw/store!Store#onBeforeChange:member",
"docComment": "/**\n * A callback before after each record's change.\n *\n * @param prev - The previous value, if any.\n *\n * @param next - The next value.\n */\n",
"docComment": "/**\n * A callback fired before each record's change.\n *\n * @param prev - The previous value, if any.\n *\n * @param next - The next value.\n */\n",
"excerptTokens": [
{
"kind": "Content",

Wyświetl plik

@ -318,7 +318,7 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
onAfterCreate?: (record: R, source: 'remote' | 'user') => void
/**
* A callback before after each record's change.
* A callback fired before each record's change.
*
* @param prev - The previous value, if any.
* @param next - The next value.

Wyświetl plik

@ -87,7 +87,6 @@ export const TldrawSelectionForeground = track(function TldrawSelectionForegroun
'select.scribble_brushing',
'select.pointing_canvas',
'select.pointing_selection',
'select.editing_shape',
'select.pointing_shape',
'select.crop.idle',
'select.crop.pointing_crop',

Wyświetl plik

@ -309,7 +309,7 @@ export class Drawing extends StateNode {
}
const hasMovedFarEnough =
Vec.Dist(pagePointWhereNextSegmentChanged, inputs.currentPagePoint) > DRAG_DISTANCE
Vec.Dist2(pagePointWhereNextSegmentChanged, inputs.currentPagePoint) > DRAG_DISTANCE
// Find the distance from where the pointer was when shift was released and
// where it is now; if it's far enough away, then update the page point where
@ -382,7 +382,7 @@ export class Drawing extends StateNode {
}
const hasMovedFarEnough =
Vec.Dist(pagePointWhereNextSegmentChanged, inputs.currentPagePoint) > DRAG_DISTANCE
Vec.Dist2(pagePointWhereNextSegmentChanged, inputs.currentPagePoint) > DRAG_DISTANCE
// Find the distance from where the pointer was when shift was released and
// where it is now; if it's far enough away, then update the page point where

Wyświetl plik

@ -160,9 +160,11 @@ export function useEditableText(id: TLShapeId, type: string, text: string) {
// This is important so that when dragging a shape using the text label,
// the shape continues to be dragged, even if the cursor is over the UI.
setPointerCapture(e.currentTarget, e)
if (!isEditing) {
setPointerCapture(e.currentTarget, e)
}
},
[editor, id]
[editor, id, isEditing]
)
const handleDoubleClick = stopEventPropagation

Wyświetl plik

@ -38,9 +38,8 @@ export class PointingShape extends StateNode {
outermostSelectingShape.id === focusedGroupId ||
// ...or if the shape is within the selection
selectedShapeIds.includes(outermostSelectingShape.id) ||
// ...or if an ancestor of the shape is selected (except note shapes)...
// todo: Consider adding a flag for this hardcoded behaviour
(selectedAncestor && selectedAncestor.type !== 'note') ||
// ...or if an ancestor of the shape is selected
selectedAncestor ||
// ...or if the current point is NOT within the selection bounds
(selectedShapeIds.length > 1 && selectionBounds?.containsPoint(currentPagePoint))
) {

Wyświetl plik

@ -19,9 +19,10 @@ export function BackToContent() {
// Rendering shapes includes all the shapes in the current page.
// We have to filter them down to just the shapes that are inside the renderingBounds.
const visibleShapes = renderingShapes.filter(
(s) => s.maskedPageBounds && renderingBounds.includes(s.maskedPageBounds)
)
const visibleShapes = renderingShapes.filter((s) => {
const maskedPageBounds = editor.getShapeMaskedPageBounds(s.id)
return maskedPageBounds && renderingBounds.includes(maskedPageBounds)
})
const showBackToContentNow =
visibleShapes.length === 0 && editor.getCurrentPageShapes().length > 0

Wyświetl plik

@ -63,42 +63,50 @@ it('updates the rendering viewport when the camera stops moving', () => {
it('lists shapes in viewport', () => {
const ids = createShapes()
editor.selectNone()
expect(editor.getRenderingShapes().map(({ id, isCulled }) => [id, isCulled])).toStrictEqual([
[ids.A, false], // A is within the expanded rendering bounds, so should not be culled; and it's in the regular viewport too, so it's on screen.
[ids.B, false],
[ids.C, false],
[ids.D, true], // D is clipped and so should always be culled / outside of viewport
])
expect(editor.getRenderingShapes().map(({ id }) => [id, editor.isShapeCulled(id)])).toStrictEqual(
[
[ids.A, false], // A is within the expanded rendering bounds, so should not be culled; and it's in the regular viewport too, so it's on screen.
[ids.B, false],
[ids.C, false],
[ids.D, true], // D is clipped and so should always be culled / outside of viewport
]
)
// Move the camera 201 pixels to the right and 201 pixels down
editor.pan({ x: -201, y: -201 })
jest.advanceTimersByTime(500)
expect(editor.getRenderingShapes().map(({ id, isCulled }) => [id, isCulled])).toStrictEqual([
[ids.A, false], // A should not be culled, even though it's no longer in the viewport (because it's still in the EXPANDED viewport)
[ids.B, false],
[ids.C, false],
[ids.D, true], // D is clipped and so should always be culled / outside of viewport
])
expect(editor.getRenderingShapes().map(({ id }) => [id, editor.isShapeCulled(id)])).toStrictEqual(
[
[ids.A, false], // A should not be culled, even though it's no longer in the viewport (because it's still in the EXPANDED viewport)
[ids.B, false],
[ids.C, false],
[ids.D, true], // D is clipped and so should always be culled / outside of viewport
]
)
editor.pan({ x: -100, y: -100 })
jest.advanceTimersByTime(500)
expect(editor.getRenderingShapes().map(({ id, isCulled }) => [id, isCulled])).toStrictEqual([
[ids.A, true], // A should be culled now that it's outside of the expanded viewport too
[ids.B, false],
[ids.C, false],
[ids.D, true], // D is clipped and so should always be culled, even if it's in the viewport
])
expect(editor.getRenderingShapes().map(({ id }) => [id, editor.isShapeCulled(id)])).toStrictEqual(
[
[ids.A, true], // A should be culled now that it's outside of the expanded viewport too
[ids.B, false],
[ids.C, false],
[ids.D, true], // D is clipped and so should always be culled, even if it's in the viewport
]
)
editor.pan({ x: -900, y: -900 })
jest.advanceTimersByTime(500)
expect(editor.getRenderingShapes().map(({ id, isCulled }) => [id, isCulled])).toStrictEqual([
[ids.A, true],
[ids.B, true],
[ids.C, true],
[ids.D, true],
])
expect(editor.getRenderingShapes().map(({ id }) => [id, editor.isShapeCulled(id)])).toStrictEqual(
[
[ids.A, true],
[ids.B, true],
[ids.C, true],
[ids.D, true],
]
)
})
it('lists shapes in viewport sorted by id with correct indexes & background indexes', () => {

Wyświetl plik

@ -176,6 +176,12 @@ export function mapObjectMapValues<Key extends string, ValueBefore, ValueAfter>(
[K in Key]: ValueAfter;
};
// @internal (undocumented)
export function measureCbDuration(name: string, cb: () => any): any;
// @internal (undocumented)
export function measureDuration(_target: any, propertyKey: string, descriptor: PropertyDescriptor): PropertyDescriptor;
// @public
export class MediaHelpers {
static getImageSize(blob: Blob): Promise<{

Wyświetl plik

@ -36,6 +36,7 @@ export {
objectMapKeys,
objectMapValues,
} from './lib/object'
export { measureCbDuration, measureDuration } from './lib/perf'
export { PngHelpers } from './lib/png'
export { type IndexKey } from './lib/reordering/IndexKey'
export {

Wyświetl plik

@ -0,0 +1,22 @@
/** @internal */
export function measureCbDuration(name: string, cb: () => any) {
const now = performance.now()
const result = cb()
// eslint-disable-next-line no-console
console.log(`${name} took`, performance.now() - now, 'ms')
return result
}
/** @internal */
export function measureDuration(_target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value
descriptor.value = function (...args: any[]) {
const start = performance.now()
const result = originalMethod.apply(this, args)
const end = performance.now()
// eslint-disable-next-line no-console
console.log(`${propertyKey} took ${end - start}ms `)
return result
}
return descriptor
}