[fix] page to screen (#1797)

This PR fixes our page to screen conversion.

### Change Type

- [x] `patch` — Bug fix

### Test Plan

1. Drop an image onto the screen while the camera is panned and zoomed.

- [x] Unit Tests
This commit is contained in:
Steve Ruiz 2023-08-06 09:27:28 +01:00 committed by GitHub
parent 8991468446
commit 16e696ed03
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 129 additions and 198 deletions

View file

@ -827,9 +827,9 @@ export class Editor extends EventEmitter<TLEventMap> {
moveShapesToPage(shapes: TLShape[], pageId: TLPageId): this;
// (undocumented)
moveShapesToPage(ids: TLShapeId[], pageId: TLPageId): this;
nudgeShapes(shapes: TLShape[], offset: VecLike, historyOptions?: CommandHistoryOptions): this;
nudgeShapes(shapes: TLShape[], offset: VecLike, historyOptions?: TLCommandHistoryOptions): this;
// (undocumented)
nudgeShapes(ids: TLShapeId[], offset: VecLike, historyOptions?: CommandHistoryOptions): this;
nudgeShapes(ids: TLShapeId[], offset: VecLike, historyOptions?: TLCommandHistoryOptions): this;
get onlySelectedShape(): null | TLShape;
get openMenus(): string[];
packShapes(shapes: TLShape[], gap: number): this;
@ -859,9 +859,9 @@ export class Editor extends EventEmitter<TLEventMap> {
registerExternalContentHandler<T extends TLExternalContent_2['type']>(type: T, handler: ((info: T extends TLExternalContent_2['type'] ? TLExternalContent_2 & {
type: T;
} : TLExternalContent_2) => void) | null): this;
renamePage(page: TLPage, name: string, historyOptions?: CommandHistoryOptions): this;
renamePage(page: TLPage, name: string, historyOptions?: TLCommandHistoryOptions): this;
// (undocumented)
renamePage(id: TLPageId, name: string, historyOptions?: CommandHistoryOptions): this;
renamePage(id: TLPageId, name: string, historyOptions?: TLCommandHistoryOptions): this;
get renderingBounds(): Box2d;
get renderingBoundsExpanded(): Box2d;
renderingBoundsMargin: number;
@ -910,9 +910,9 @@ export class Editor extends EventEmitter<TLEventMap> {
sendToBack(ids: TLShapeId[]): this;
setCamera(point: VecLike, animation?: TLAnimationOptions): this;
setCroppingShapeId(id: null | TLShapeId): this;
setCurrentPage(page: TLPage, historyOptions?: CommandHistoryOptions): this;
setCurrentPage(page: TLPage, historyOptions?: TLCommandHistoryOptions): this;
// (undocumented)
setCurrentPage(pageId: TLPageId, historyOptions?: CommandHistoryOptions): this;
setCurrentPage(pageId: TLPageId, historyOptions?: TLCommandHistoryOptions): this;
setCurrentTool(id: string, info?: {}): this;
setCursor: (cursor: Partial<TLCursor>) => this;
setEditingShapeId(id: null | TLShapeId): this;
@ -920,9 +920,9 @@ export class Editor extends EventEmitter<TLEventMap> {
setFocusedGroupId(next: null | TLShapeId): this;
setHintingIds(ids: TLShapeId[]): this;
setHoveredShapeId(id: null | TLShapeId): this;
setOpacity(opacity: number, historyOptions?: CommandHistoryOptions): this;
setSelectedShapeIds(ids: TLShapeId[], historyOptions?: CommandHistoryOptions): this;
setStyle<T>(style: StyleProp<T>, value: T, historyOptions?: CommandHistoryOptions): this;
setOpacity(opacity: number, historyOptions?: TLCommandHistoryOptions): this;
setSelectedShapeIds(ids: TLShapeId[], historyOptions?: TLCommandHistoryOptions): this;
setStyle<T>(style: StyleProp<T>, value: T, historyOptions?: TLCommandHistoryOptions): this;
shapeUtils: {
readonly [K in string]?: ShapeUtil<TLUnknownShape>;
};
@ -959,14 +959,14 @@ export class Editor extends EventEmitter<TLEventMap> {
// (undocumented)
ungroupShapes(ids: TLShape[]): this;
updateAssets(assets: TLAssetPartial[]): this;
updateCurrentPageState(partial: Partial<Omit<TLInstancePageState, 'editingShapeId' | 'focusedGroupId' | 'pageId' | 'selectedShapeIds'>>, historyOptions?: CommandHistoryOptions): this;
updateCurrentPageState(partial: Partial<Omit<TLInstancePageState, 'editingShapeId' | 'focusedGroupId' | 'pageId' | 'selectedShapeIds'>>, historyOptions?: TLCommandHistoryOptions): this;
updateDocumentSettings(settings: Partial<TLDocument>): this;
updateInstanceState(partial: Partial<Omit<TLInstance, 'currentPageId'>>, historyOptions?: CommandHistoryOptions): this;
updatePage(partial: RequiredKeys<TLPage, 'id'>, historyOptions?: CommandHistoryOptions): this;
updateInstanceState(partial: Partial<Omit<TLInstance, 'currentPageId'>>, historyOptions?: TLCommandHistoryOptions): this;
updatePage(partial: RequiredKeys<TLPage, 'id'>, historyOptions?: TLCommandHistoryOptions): this;
// @internal
updateRenderingBounds(): this;
updateShape<T extends TLUnknownShape>(partial: null | TLShapePartial<T> | undefined, historyOptions?: CommandHistoryOptions): this;
updateShapes<T extends TLUnknownShape>(partials: (null | TLShapePartial<T> | undefined)[], historyOptions?: CommandHistoryOptions): this;
updateShape<T extends TLUnknownShape>(partial: null | TLShapePartial<T> | undefined, historyOptions?: TLCommandHistoryOptions): this;
updateShapes<T extends TLUnknownShape>(partials: (null | TLShapePartial<T> | undefined)[], historyOptions?: TLCommandHistoryOptions): this;
updateViewportScreenBounds(center?: boolean): this;
readonly user: UserPreferencesManager;
get viewportPageBounds(): Box2d;

View file

@ -105,7 +105,7 @@ import { parentsToChildren } from './derivations/parentsToChildren'
import { deriveShapeIdsInCurrentPage } from './derivations/shapeIdsInCurrentPage'
import { ClickManager } from './managers/ClickManager'
import { EnvironmentManager } from './managers/EnvironmentManager'
import { CommandHistoryOptions, HistoryManager } from './managers/HistoryManager'
import { HistoryManager } from './managers/HistoryManager'
import { SideEffectManager } from './managers/SideEffectManager'
import { SnapManager } from './managers/SnapManager'
import { TextManager } from './managers/TextManager'
@ -122,6 +122,7 @@ import { SvgExportContext, SvgExportDef } from './types/SvgExportContext'
import { TLContent } from './types/clipboard-types'
import { TLEventMap } from './types/emit-types'
import { TLEventInfo, TLPinchEventInfo, TLPointerEventInfo } from './types/event-types'
import { TLCommandHistoryOptions } from './types/history-types'
import { OptionalKeys, RequiredKeys } from './types/misc-types'
import { TLResizeHandle } from './types/selection-types'
@ -1185,7 +1186,7 @@ export class Editor extends EventEmitter<TLEventMap> {
*/
updateInstanceState(
partial: Partial<Omit<TLInstance, 'currentPageId'>>,
historyOptions?: CommandHistoryOptions
historyOptions?: TLCommandHistoryOptions
): this {
this._updateInstanceState(partial, { ephemeral: true, squashing: true, ...historyOptions })
@ -1207,7 +1208,7 @@ export class Editor extends EventEmitter<TLEventMap> {
'updateInstanceState',
(
partial: Partial<Omit<TLInstance, 'currentPageId'>>,
historyOptions?: CommandHistoryOptions
historyOptions?: TLCommandHistoryOptions
) => {
const prev = this.instanceState
const next = { ...prev, ...partial }
@ -1368,7 +1369,7 @@ export class Editor extends EventEmitter<TLEventMap> {
partial: Partial<
Omit<TLInstancePageState, 'selectedShapeIds' | 'editingShapeId' | 'pageId' | 'focusedGroupId'>
>,
historyOptions?: CommandHistoryOptions
historyOptions?: TLCommandHistoryOptions
): this {
this._setInstancePageState(partial, historyOptions)
return this
@ -1379,7 +1380,7 @@ export class Editor extends EventEmitter<TLEventMap> {
'setInstancePageState',
(
partial: Partial<Omit<TLInstancePageState, 'selectedShapeIds'>>,
historyOptions?: CommandHistoryOptions
historyOptions?: TLCommandHistoryOptions
) => {
const prev = this.store.get(partial.id ?? this.currentPageState.id)!
return { data: { prev, partial }, ...historyOptions }
@ -1417,7 +1418,7 @@ export class Editor extends EventEmitter<TLEventMap> {
*
* @public
*/
setSelectedShapeIds(ids: TLShapeId[], historyOptions?: CommandHistoryOptions): this {
setSelectedShapeIds(ids: TLShapeId[], historyOptions?: TLCommandHistoryOptions): this {
this._setSelectedShapeIds(ids, historyOptions)
return this
}
@ -1425,7 +1426,7 @@ export class Editor extends EventEmitter<TLEventMap> {
/** @internal */
private _setSelectedShapeIds = this.history.createCommand(
'setSelectedShapeIds',
(ids: TLShapeId[], historyOptions?: CommandHistoryOptions) => {
(ids: TLShapeId[], historyOptions?: TLCommandHistoryOptions) => {
const { selectedShapeIds: prevSelectedShapeIds } = this.currentPageState
const prevSet = new Set(prevSelectedShapeIds)
@ -2693,8 +2694,8 @@ export class Editor extends EventEmitter<TLEventMap> {
const { screenBounds } = this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!
const { x: cx, y: cy, z: cz = 1 } = this.camera
return {
x: (point.x - screenBounds.x - cx) / cz,
y: (point.y - screenBounds.y - cy) / cz,
x: (point.x - screenBounds.x) / cz - cx,
y: (point.y - screenBounds.y) / cz - cy,
z: point.z ?? 0.5,
}
}
@ -2716,8 +2717,8 @@ export class Editor extends EventEmitter<TLEventMap> {
const { x: cx, y: cy, z: cz = 1 } = this.camera
return {
x: point.x * cz + cx + screenBounds.x,
y: point.y * cz + cy + screenBounds.y,
x: (point.x + cx) * cz + screenBounds.x,
y: (point.y + cy) * cz + screenBounds.y,
z: point.z ?? 0.5,
}
}
@ -3218,9 +3219,9 @@ export class Editor extends EventEmitter<TLEventMap> {
*
* @public
*/
setCurrentPage(page: TLPage, historyOptions?: CommandHistoryOptions): this
setCurrentPage(pageId: TLPageId, historyOptions?: CommandHistoryOptions): this
setCurrentPage(arg: TLPageId | TLPage, historyOptions?: CommandHistoryOptions): this {
setCurrentPage(page: TLPage, historyOptions?: TLCommandHistoryOptions): this
setCurrentPage(pageId: TLPageId, historyOptions?: TLCommandHistoryOptions): this
setCurrentPage(arg: TLPageId | TLPage, historyOptions?: TLCommandHistoryOptions): this {
const pageId = typeof arg === 'string' ? arg : arg.id
this._setCurrentPageId(pageId, historyOptions)
return this
@ -3228,7 +3229,7 @@ export class Editor extends EventEmitter<TLEventMap> {
/** @internal */
private _setCurrentPageId = this.history.createCommand(
'setCurrentPage',
(pageId: TLPageId, historyOptions?: CommandHistoryOptions) => {
(pageId: TLPageId, historyOptions?: TLCommandHistoryOptions) => {
if (!this.store.has(pageId)) {
console.error("Tried to set the current page id to a page that doesn't exist.")
return
@ -3295,14 +3296,14 @@ export class Editor extends EventEmitter<TLEventMap> {
*
* @public
*/
updatePage(partial: RequiredKeys<TLPage, 'id'>, historyOptions?: CommandHistoryOptions): this {
updatePage(partial: RequiredKeys<TLPage, 'id'>, historyOptions?: TLCommandHistoryOptions): this {
this._updatePage(partial, historyOptions)
return this
}
/** @internal */
private _updatePage = this.history.createCommand(
'updatePage',
(partial: RequiredKeys<TLPage, 'id'>, historyOptions?: CommandHistoryOptions) => {
(partial: RequiredKeys<TLPage, 'id'>, historyOptions?: TLCommandHistoryOptions) => {
if (this.instanceState.isReadonly) return null
const prev = this.getPage(partial.id)
@ -3513,9 +3514,9 @@ export class Editor extends EventEmitter<TLEventMap> {
*
* @public
*/
renamePage(page: TLPage, name: string, historyOptions?: CommandHistoryOptions): this
renamePage(id: TLPageId, name: string, historyOptions?: CommandHistoryOptions): this
renamePage(arg: TLPageId | TLPage, name: string, historyOptions?: CommandHistoryOptions) {
renamePage(page: TLPage, name: string, historyOptions?: TLCommandHistoryOptions): this
renamePage(id: TLPageId, name: string, historyOptions?: TLCommandHistoryOptions): this
renamePage(arg: TLPageId | TLPage, name: string, historyOptions?: TLCommandHistoryOptions) {
const id = typeof arg === 'string' ? arg : arg.id
if (this.instanceState.isReadonly) return this
this.updatePage({ id, name }, historyOptions)
@ -4966,12 +4967,12 @@ export class Editor extends EventEmitter<TLEventMap> {
* @param direction - The direction in which to move the shapes.
* @param historyOptions - (optional) The history options for the change.
*/
nudgeShapes(shapes: TLShape[], offset: VecLike, historyOptions?: CommandHistoryOptions): this
nudgeShapes(ids: TLShapeId[], offset: VecLike, historyOptions?: CommandHistoryOptions): this
nudgeShapes(shapes: TLShape[], offset: VecLike, historyOptions?: TLCommandHistoryOptions): this
nudgeShapes(ids: TLShapeId[], offset: VecLike, historyOptions?: TLCommandHistoryOptions): this
nudgeShapes(
arg: TLShapeId[] | TLShape[],
offset: VecLike,
historyOptions?: CommandHistoryOptions
historyOptions?: TLCommandHistoryOptions
): this {
const ids =
typeof arg[0] === 'string' ? (arg as TLShapeId[]) : (arg as TLShape[]).map((s) => s.id)
@ -6815,7 +6816,7 @@ export class Editor extends EventEmitter<TLEventMap> {
*/
updateShape<T extends TLUnknownShape>(
partial: TLShapePartial<T> | null | undefined,
historyOptions?: CommandHistoryOptions
historyOptions?: TLCommandHistoryOptions
) {
this.updateShapes([partial], historyOptions)
return this
@ -6836,7 +6837,7 @@ export class Editor extends EventEmitter<TLEventMap> {
*/
updateShapes<T extends TLUnknownShape>(
partials: (TLShapePartial<T> | null | undefined)[],
historyOptions?: CommandHistoryOptions
historyOptions?: TLCommandHistoryOptions
) {
let compactedPartials = compact(partials)
if (this.animatingShapes.size > 0) {
@ -6859,7 +6860,10 @@ export class Editor extends EventEmitter<TLEventMap> {
/** @internal */
private _updateShapes = this.history.createCommand(
'updateShapes',
(_partials: (TLShapePartial | null | undefined)[], historyOptions?: CommandHistoryOptions) => {
(
_partials: (TLShapePartial | null | undefined)[],
historyOptions?: TLCommandHistoryOptions
) => {
if (this.instanceState.isReadonly) return null
const partials = compact(_partials)
@ -7205,7 +7209,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @param opacity - The opacity to set. Must be a number between 0 and 1 inclusive.
* @param historyOptions - The history options for the change.
*/
setOpacity(opacity: number, historyOptions?: CommandHistoryOptions): this {
setOpacity(opacity: number, historyOptions?: TLCommandHistoryOptions): this {
this.history.batch(() => {
if (this.isIn('select')) {
const {
@ -7269,7 +7273,7 @@ export class Editor extends EventEmitter<TLEventMap> {
*
* @public
*/
setStyle<T>(style: StyleProp<T>, value: T, historyOptions?: CommandHistoryOptions): this {
setStyle<T>(style: StyleProp<T>, value: T, historyOptions?: TLCommandHistoryOptions): this {
this.history.batch(() => {
if (this.isIn('select')) {
const {

View file

@ -1,4 +1,5 @@
import { CommandHistoryOptions, HistoryManager } from './HistoryManager'
import { TLCommandHistoryOptions } from '../types/history-types'
import { HistoryManager } from './HistoryManager'
import { stack } from './Stack'
function createCounterHistoryManager() {
@ -291,8 +292,8 @@ describe('history options', () => {
let manager: HistoryManager<any>
let state: { a: number; b: number }
let setA: (n: number, historyOptions?: CommandHistoryOptions) => any
let setB: (n: number, historyOptions?: CommandHistoryOptions) => any
let setA: (n: number, historyOptions?: TLCommandHistoryOptions) => any
let setB: (n: number, historyOptions?: TLCommandHistoryOptions) => any
beforeEach(() => {
manager = new HistoryManager({ emit: () => void null }, () => {
@ -306,7 +307,7 @@ describe('history options', () => {
setA = manager.createCommand(
'setA',
(n: number, historyOptions?: CommandHistoryOptions) => ({
(n: number, historyOptions?: TLCommandHistoryOptions) => ({
data: { next: n, prev: state.a },
...historyOptions,
}),
@ -323,7 +324,7 @@ describe('history options', () => {
setB = manager.createCommand(
'setB',
(n: number, historyOptions?: CommandHistoryOptions) => ({
(n: number, historyOptions?: TLCommandHistoryOptions) => ({
data: { next: n, prev: state.b },
...historyOptions,
}),

View file

@ -1,29 +1,13 @@
import { atom, transact } from '@tldraw/state'
import { devFreeze } from '@tldraw/store'
import { uniqueId } from '../../utils/uniqueId'
import { TLCommandHandler, TLHistoryEntry } from '../types/history-types'
import { TLCommandHandler, TLCommandHistoryOptions, TLHistoryEntry } from '../types/history-types'
import { Stack, stack } from './Stack'
/** @public */
export type CommandHistoryOptions = Partial<{
/**
* When true, this command will be squashed with the previous command in the undo / redo stack.
*/
squashing: boolean
/**
* When true, this command will not add anything to the undo / redo stack. Its change will never be undone or redone.
*/
ephemeral: boolean
/**
* When true, adding this this command will not clear out the redo stack.
*/
preservesRedoStack: boolean
}>
type CommandFn<Data> = (...args: any[]) =>
| ({
data: Data
} & CommandHistoryOptions)
} & TLCommandHistoryOptions)
| null
| undefined
| void

View file

@ -1,3 +1,19 @@
/** @public */
export type TLCommandHistoryOptions = Partial<{
/**
* When true, this command will be squashed with the previous command in the undo / redo stack.
*/
squashing: boolean
/**
* When true, this command will not add anything to the undo / redo stack. Its change will never be undone or redone.
*/
ephemeral: boolean
/**
* When true, adding this this command will not clear out the redo stack.
*/
preservesRedoStack: boolean
}>
/** @public */
export type TLHistoryMark = {
type: 'STOP'

View file

@ -1,3 +1,4 @@
import { VecLike } from '@tldraw/editor'
import { TestEditor } from '../TestEditor'
let editor: TestEditor
@ -6,126 +7,70 @@ beforeEach(() => {
editor = new TestEditor()
})
function checkScreenPage(screen: VecLike, page: VecLike) {
const pageResult = editor.screenToPage(screen)
expect(pageResult).toMatchObject(page)
const screenResult = editor.pageToScreen(pageResult)
expect(screenResult).toMatchObject(screen)
}
describe('viewport.screenToPage', () => {
it('converts correctly', () => {
expect(editor.screenToPage({ x: 0, y: 0 })).toMatchObject({ x: 0, y: 0 })
expect(editor.pageToScreen({ x: 0, y: 0 })).toMatchObject({ x: 0, y: 0 })
expect(editor.screenToPage({ x: 100, y: 100 })).toMatchObject({ x: 100, y: 100 })
expect(editor.pageToScreen({ x: 100, y: 100 })).toMatchObject({ x: 100, y: 100 })
expect(editor.screenToPage({ x: -100, y: -100 })).toMatchObject({ x: -100, y: -100 })
expect(editor.pageToScreen({ x: -100, y: -100 })).toMatchObject({ x: -100, y: -100 })
checkScreenPage({ x: 0, y: 0 }, { x: 0, y: 0 })
checkScreenPage({ x: 100, y: 100 }, { x: 100, y: 100 })
checkScreenPage({ x: -100, y: -100 }, { x: -100, y: -100 })
})
it('converts correctly when zoomed', () => {
editor.setCamera({ x: 0, y: 0, z: 0.5 })
expect(editor.screenToPage({ x: 0, y: 0 })).toMatchObject({ x: 0, y: 0 })
expect(editor.pageToScreen({ x: 0, y: 0 })).toMatchObject({ x: 0, y: 0 })
expect(editor.screenToPage({ x: 100, y: 100 })).toMatchObject({ x: 200, y: 200 })
expect(editor.pageToScreen({ x: 200, y: 200 })).toMatchObject({ x: 100, y: 100 })
expect(editor.screenToPage({ x: -100, y: -100 })).toMatchObject({ x: -200, y: -200 })
expect(editor.pageToScreen({ x: -200, y: -200 })).toMatchObject({ x: -100, y: -100 })
checkScreenPage({ x: 0, y: 0 }, { x: 0, y: 0 })
checkScreenPage({ x: 100, y: 100 }, { x: 200, y: 200 })
checkScreenPage({ x: -100, y: -100 }, { x: -200, y: -200 })
})
it('converts correctly when panned', () => {
editor.setCamera({ x: 100, y: 100 })
expect(editor.screenToPage({ x: 0, y: 0 })).toMatchObject({ x: -100, y: -100 })
expect(editor.pageToScreen({ x: -100, y: -100 })).toMatchObject({ x: 0, y: 0 })
expect(editor.screenToPage({ x: 100, y: 100 })).toMatchObject({ x: 0, y: 0 })
expect(editor.pageToScreen({ x: 0, y: 0 })).toMatchObject({ x: 100, y: 100 })
expect(editor.screenToPage({ x: -100, y: -100 })).toMatchObject({ x: -200, y: -200 })
expect(editor.pageToScreen({ x: -200, y: -200 })).toMatchObject({ x: -100, y: -100 })
checkScreenPage({ x: 0, y: 0 }, { x: -100, y: -100 })
checkScreenPage({ x: 100, y: 100 }, { x: 0, y: 0 })
checkScreenPage({ x: -100, y: -100 }, { x: -200, y: -200 })
})
it('converts correctly when panned and zoomed', () => {
editor.setCamera({ x: 100, y: 100, z: 0.5 })
expect(editor.screenToPage({ x: 0, y: 0 })).toMatchObject({ x: -200, y: -200 })
expect(editor.pageToScreen({ x: -200, y: -200 })).toMatchObject({ x: 0, y: 0 })
expect(editor.screenToPage({ x: 100, y: 100 })).toMatchObject({ x: 0, y: 0 })
expect(editor.pageToScreen({ x: 0, y: 0 })).toMatchObject({ x: 100, y: 100 })
expect(editor.screenToPage({ x: -100, y: -100 })).toMatchObject({ x: -400, y: -400 })
expect(editor.pageToScreen({ x: -400, y: -400 })).toMatchObject({ x: -100, y: -100 })
checkScreenPage({ x: 0, y: 0 }, { x: -100, y: -100 })
checkScreenPage({ x: 100, y: 100 }, { x: 100, y: 100 })
checkScreenPage({ x: -100, y: -100 }, { x: -300, y: -300 })
checkScreenPage({ x: -150, y: -150 }, { x: -400, y: -400 })
})
it('converts correctly when offset', () => {
// move the editor's page bounds down and to the left by 100, 100
// 0,0 s
// +------------------------+
// | 100,100 s |
// | c-----------------+ |
// | | 0,0 p | |
// | | | |
editor.updateInstanceState({ screenBounds: { ...editor.viewportScreenBounds, x: 100, y: 100 } })
expect(editor.screenToPage({ x: 0, y: 0 })).toMatchObject({ x: -100, y: -100 })
expect(editor.pageToScreen({ x: -100, y: -100 })).toMatchObject({ x: 0, y: 0 })
expect(editor.screenToPage({ x: -100, y: -100 })).toMatchObject({ x: -200, y: -200 })
expect(editor.pageToScreen({ x: -200, y: -200 })).toMatchObject({ x: -100, y: -100 })
expect(editor.screenToPage({ x: 100, y: 100 })).toMatchObject({ x: 0, y: 0 })
expect(editor.pageToScreen({ x: 0, y: 0 })).toMatchObject({ x: 100, y: 100 })
// 0,0 s
// c------------------------+
// | 100,100 s |
// | +-----------------+ |
// | | 100,100 p | |
// | | | |
editor.setCamera({ x: -100, y: -100 }) // -100, -100
expect(editor.screenToPage({ x: -100, y: -100 })).toMatchObject({ x: -100, y: -100 })
expect(editor.pageToScreen({ x: -100, y: -100 })).toMatchObject({ x: -100, y: -100 })
expect(editor.screenToPage({ x: 0, y: 0 })).toMatchObject({ x: 0, y: 0 })
expect(editor.pageToScreen({ x: 0, y: 0 })).toMatchObject({ x: 0, y: 0 })
expect(editor.screenToPage({ x: 100, y: 100 })).toMatchObject({ x: 100, y: 100 })
expect(editor.pageToScreen({ x: 100, y: 100 })).toMatchObject({ x: 100, y: 100 })
// 0,0 s no offset, zoom at 50%
// c------------------------+
// | 0,0 p |
// | |
// | |
// | |
editor.setCamera({ x: 0, y: 0, z: 0.5 })
editor.updateInstanceState({ screenBounds: { ...editor.viewportScreenBounds, x: 0, y: 0 } })
expect(editor.screenToPage({ x: 0, y: 0 })).toMatchObject({ x: 0, y: 0 })
expect(editor.pageToScreen({ x: 0, y: 0 })).toMatchObject({ x: 0, y: 0 })
expect(editor.screenToPage({ x: -100, y: -100 })).toMatchObject({ x: -200, y: -200 })
expect(editor.pageToScreen({ x: -200, y: -200 })).toMatchObject({ x: -100, y: -100 })
expect(editor.screenToPage({ x: 100, y: 100 })).toMatchObject({ x: 200, y: 200 })
expect(editor.pageToScreen({ x: 200, y: 200 })).toMatchObject({ x: 100, y: 100 })
checkScreenPage({ x: 0, y: 0 }, { x: -100, y: -100 })
checkScreenPage({ x: -100, y: -100 }, { x: -200, y: -200 })
checkScreenPage({ x: 100, y: 100 }, { x: 0, y: 0 })
})
it('converts correctly when zoomed out', () => {
// camera at zero, screenbounds at zero, but zoom at .5
editor.setCamera({ x: 0, y: 0, z: 0.5 })
editor.updateInstanceState({ screenBounds: { ...editor.viewportScreenBounds, x: 0, y: 0 } })
expect(editor.screenToPage({ x: 0, y: 0 })).toMatchObject({ x: 0, y: 0 })
expect(editor.pageToScreen({ x: 0, y: 0 })).toMatchObject({ x: 0, y: 0 })
expect(editor.screenToPage({ x: -100, y: -100 })).toMatchObject({ x: -200, y: -200 })
expect(editor.pageToScreen({ x: -200, y: -200 })).toMatchObject({ x: -100, y: -100 })
expect(editor.screenToPage({ x: 100, y: 100 })).toMatchObject({ x: 200, y: 200 })
expect(editor.pageToScreen({ x: 200, y: 200 })).toMatchObject({ x: 100, y: 100 })
checkScreenPage({ x: 0, y: 0 }, { x: 0, y: 0 })
checkScreenPage({ x: -100, y: -100 }, { x: -200, y: -200 })
checkScreenPage({ x: 100, y: 100 }, { x: 200, y: 200 })
})
it('converts correctly when zoomed in', () => {
editor.setCamera({ x: 0, y: 0, z: 2 })
editor.updateInstanceState({ screenBounds: { ...editor.viewportScreenBounds, x: 0, y: 0 } })
expect(editor.screenToPage({ x: 0, y: 0 })).toMatchObject({ x: 0, y: 0 })
expect(editor.screenToPage({ x: -100, y: -100 })).toMatchObject({ x: -50, y: -50 })
expect(editor.screenToPage({ x: 100, y: 100 })).toMatchObject({ x: 50, y: 50 })
checkScreenPage({ x: 0, y: 0 }, { x: 0, y: 0 })
checkScreenPage({ x: -100, y: -100 }, { x: -50, y: -50 })
checkScreenPage({ x: 100, y: 100 }, { x: 50, y: 50 })
})
it('converts correctly when zoomed', () => {
@ -133,82 +78,63 @@ describe('viewport.screenToPage', () => {
editor.updateInstanceState({ screenBounds: { ...editor.viewportScreenBounds, x: 0, y: 0 } })
editor.setCamera({ x: 0, y: 0, z: 0.5 })
// zero point, where page and screen are the same
expect(editor.pageToScreen({ x: 0, y: 0 })).toMatchObject({ x: 0, y: 0 })
expect(editor.screenToPage({ x: 0, y: 0 })).toMatchObject({ x: 0, y: 0 })
checkScreenPage({ x: 0, y: 0 }, { x: 0, y: 0 })
checkScreenPage({ x: -100, y: -100 }, { x: -200, y: -200 })
checkScreenPage({ x: 100, y: 100 }, { x: 200, y: 200 })
})
expect(editor.pageToScreen({ x: 100, y: 100 })).toMatchObject({ x: 50, y: 50 })
expect(editor.screenToPage({ x: 50, y: 50 })).toMatchObject({ x: 100, y: 100 })
it('converts correctly when offset and zoomed', () => {
editor.setCamera({ x: 0, y: 0, z: 0.5 })
editor.updateInstanceState({ screenBounds: { ...editor.viewportScreenBounds, x: 100, y: 100 } })
expect(editor.pageToScreen({ x: 200, y: 200 })).toMatchObject({ x: 100, y: 100 })
expect(editor.screenToPage({ x: 100, y: 100 })).toMatchObject({ x: 200, y: 200 })
checkScreenPage({ x: 0, y: 0 }, { x: -200, y: -200 })
checkScreenPage({ x: -100, y: -100 }, { x: -400, y: -400 })
checkScreenPage({ x: 100, y: 100 }, { x: 0, y: 0 })
})
it('converts correctly when zoomed and panned', () => {
editor.updateInstanceState({ screenBounds: { ...editor.viewportScreenBounds, x: 0, y: 0 } })
editor.setCamera({ x: 100, y: 100, z: 0.5 })
expect(editor.pageToScreen({ x: 0, y: 0 })).toMatchObject({ x: 100, y: 100 })
expect(editor.screenToPage({ x: 100, y: 100 })).toMatchObject({ x: 0, y: 0 })
expect(editor.pageToScreen({ x: 100, y: 100 })).toMatchObject({ x: 150, y: 150 })
expect(editor.screenToPage({ x: 150, y: 150 })).toMatchObject({ x: 100, y: 100 })
// zero point, where page and screen are the same
expect(editor.pageToScreen({ x: 200, y: 200 })).toMatchObject({ x: 200, y: 200 })
expect(editor.screenToPage({ x: 200, y: 200 })).toMatchObject({ x: 200, y: 200 })
checkScreenPage({ x: 0, y: 0 }, { x: -100, y: -100 })
checkScreenPage({ x: -100, y: -100 }, { x: -300, y: -300 })
checkScreenPage({ x: 100, y: 100 }, { x: 100, y: 100 })
})
it('converts correctly when offset', () => {
editor.updateInstanceState({ screenBounds: { ...editor.viewportScreenBounds, x: 100, y: 100 } })
editor.setCamera({ x: 0, y: 0, z: 0.5 })
expect(editor.pageToScreen({ x: 0, y: 0 })).toMatchObject({ x: 100, y: 100 })
expect(editor.pageToScreen({ x: 100, y: 100 })).toMatchObject({ x: 150, y: 150 })
expect(editor.pageToScreen({ x: 200, y: 200 })).toMatchObject({ x: 200, y: 200 })
expect(editor.screenToPage({ x: 0, y: 0 })).toMatchObject({ x: -200, y: -200 })
expect(editor.screenToPage({ x: 100, y: 100 })).toMatchObject({ x: 0, y: 0 })
expect(editor.screenToPage({ x: 200, y: 200 })).toMatchObject({ x: 200, y: 200 })
expect(editor.screenToPage({ x: 300, y: 300 })).toMatchObject({ x: 400, y: 400 })
checkScreenPage({ x: 0, y: 0 }, { x: -200, y: -200 })
checkScreenPage({ x: 100, y: 100 }, { x: 0, y: 0 })
checkScreenPage({ x: 200, y: 200 }, { x: 200, y: 200 })
})
it('converts correctly when panned', () => {
editor.updateInstanceState({ screenBounds: { ...editor.viewportScreenBounds, x: 0, y: 0 } })
editor.setCamera({ x: 100, y: 100, z: 1 })
expect(editor.pageToScreen({ x: 0, y: 0 })).toMatchObject({ x: 100, y: 100 })
expect(editor.pageToScreen({ x: 100, y: 100 })).toMatchObject({ x: 200, y: 200 })
expect(editor.pageToScreen({ x: 200, y: 200 })).toMatchObject({ x: 300, y: 300 })
expect(editor.screenToPage({ x: 100, y: 100 })).toMatchObject({ x: 0, y: 0 })
expect(editor.screenToPage({ x: 200, y: 200 })).toMatchObject({ x: 100, y: 100 })
expect(editor.screenToPage({ x: 300, y: 300 })).toMatchObject({ x: 200, y: 200 })
checkScreenPage({ x: 0, y: 0 }, { x: -100, y: -100 })
checkScreenPage({ x: 100, y: 100 }, { x: 0, y: 0 })
checkScreenPage({ x: 200, y: 200 }, { x: 100, y: 100 })
})
it('converts correctly when panned and zoomed', () => {
editor.updateInstanceState({ screenBounds: { ...editor.viewportScreenBounds, x: 0, y: 0 } })
editor.setCamera({ x: 100, y: 100, z: 0.5 })
expect(editor.pageToScreen({ x: 0, y: 0 })).toMatchObject({ x: 100, y: 100 })
expect(editor.pageToScreen({ x: 100, y: 100 })).toMatchObject({ x: 150, y: 150 })
expect(editor.pageToScreen({ x: 200, y: 200 })).toMatchObject({ x: 200, y: 200 })
expect(editor.screenToPage({ x: 100, y: 100 })).toMatchObject({ x: 0, y: 0 })
expect(editor.screenToPage({ x: 150, y: 150 })).toMatchObject({ x: 100, y: 100 })
expect(editor.screenToPage({ x: 200, y: 200 })).toMatchObject({ x: 200, y: 200 })
checkScreenPage({ x: 0, y: 0 }, { x: -100, y: -100 })
checkScreenPage({ x: 100, y: 100 }, { x: 100, y: 100 })
checkScreenPage({ x: 200, y: 200 }, { x: 300, y: 300 })
})
it('converts correctly when panned and zoomed and offset', () => {
editor.updateInstanceState({ screenBounds: { ...editor.viewportScreenBounds, x: 100, y: 100 } })
editor.setCamera({ x: 100, y: 100, z: 0.5 })
expect(editor.pageToScreen({ x: 0, y: 0 })).toMatchObject({ x: 200, y: 200 })
expect(editor.pageToScreen({ x: 100, y: 100 })).toMatchObject({ x: 250, y: 250 })
expect(editor.pageToScreen({ x: 200, y: 200 })).toMatchObject({ x: 300, y: 300 })
expect(editor.screenToPage({ x: 200, y: 200 })).toMatchObject({ x: 0, y: 0 })
expect(editor.screenToPage({ x: 250, y: 250 })).toMatchObject({ x: 100, y: 100 })
expect(editor.screenToPage({ x: 300, y: 300 })).toMatchObject({ x: 200, y: 200 })
checkScreenPage({ x: 0, y: 0 }, { x: -300, y: -300 })
checkScreenPage({ x: 100, y: 100 }, { x: -100, y: -100 })
checkScreenPage({ x: 200, y: 200 }, { x: 100, y: 100 })
})
})

View file

@ -84,7 +84,7 @@ describe('When center is false', () => {
editor.setCamera({ x: -100, y: -100, z: 1 })
expect(editor.screenToPage({ x: 0, y: 0 })).toMatchObject({ x: 100, y: 100 })
editor.setCamera({ x: -100, y: -100, z: 2 })
expect(editor.screenToPage({ x: 0, y: 0 })).toMatchObject({ x: 50, y: 50 })
expect(editor.screenToPage({ x: 0, y: 0 })).toMatchObject({ x: 100, y: 100 })
editor.setScreenBounds({ x: 100, y: 100, w: 500, h: 600 }, false)
expect(editor.viewportScreenBounds).toMatchObject({
@ -93,7 +93,7 @@ describe('When center is false', () => {
w: 500,
h: 600,
})
expect(editor.screenToPage({ x: 0, y: 0 })).toMatchObject({ x: 0, y: 0 })
expect(editor.screenToPage({ x: 0, y: 0 })).toMatchObject({ x: 50, y: 50 })
})
})