[fix] Fixes off-center bugs (#101)

* moves center from window center to center of element

* Removes onMount in Renderer, adds onBoundsChange

* Fix centered-g css

* Fix zoom to fit
This commit is contained in:
Steve Ruiz 2021-09-22 09:45:09 +01:00 committed by GitHub
parent 7d61d24398
commit 68efbf69fa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 125 additions and 104 deletions

View file

@ -34,7 +34,7 @@ export function Page<T extends TLShape, M extends Record<string, unknown>>({
page, page,
pageState, pageState,
shapeUtils, shapeUtils,
inputs.size, [inputs.bounds.width, inputs.bounds.height],
meta, meta,
callbacks.onRenderCountChange callbacks.onRenderCountChange
) )
@ -59,7 +59,7 @@ export function Page<T extends TLShape, M extends Record<string, unknown>>({
<Bounds <Bounds
zoom={zoom} zoom={zoom}
bounds={bounds} bounds={bounds}
viewportWidth={inputs.size[0]} viewportWidth={inputs.bounds.width}
isLocked={isLocked} isLocked={isLocked}
rotation={rotation} rotation={rotation}
/> />

View file

@ -56,9 +56,13 @@ export interface RendererProps<T extends TLShape, E extends Element = any, M = a
*/ */
meta?: M meta?: M
/** /**
* A callback that receives the renderer's inputs manager. * (optional) A callback that receives the renderer's inputs manager.
*/ */
onMount?: (inputs: Inputs) => void onMount?: (inputs: Inputs) => void
/**
* (optional) A callback that is fired when the editor's client bounding box changes.
*/
onBoundsChange?: (bounds: TLBounds) => void
} }
/** /**
@ -83,7 +87,7 @@ export function Renderer<T extends TLShape, E extends Element, M extends Record<
}: RendererProps<T, E, M>): JSX.Element { }: RendererProps<T, E, M>): JSX.Element {
useTLTheme(theme) useTLTheme(theme)
const rScreenBounds = React.useRef<TLBounds>(null) const rSelectionBounds = React.useRef<TLBounds>(null)
const rPageState = React.useRef<TLPageState>(pageState) const rPageState = React.useRef<TLPageState>(pageState)
@ -96,7 +100,7 @@ export function Renderer<T extends TLShape, E extends Element, M extends Record<
const [context] = React.useState<TLContextType<T, E, M>>(() => ({ const [context] = React.useState<TLContextType<T, E, M>>(() => ({
callbacks: rest, callbacks: rest,
shapeUtils, shapeUtils,
rScreenBounds, rSelectionBounds,
rPageState, rPageState,
inputs: new Inputs(), inputs: new Inputs(),
})) }))

View file

@ -15,17 +15,5 @@ export function useCameraCss(ref: React.RefObject<HTMLDivElement>, pageState: TL
ref.current!.style.setProperty('--tl-camera-y', pageState.camera.point[1] + 'px') ref.current!.style.setProperty('--tl-camera-y', pageState.camera.point[1] + 'px')
}, [pageState.camera.point]) }, [pageState.camera.point])
// Update the group's position when the camera moves or zooms
// React.useEffect(() => {
// const {
// zoom,
// point: [x = 0, y = 0],
// } = pageState.camera
// rLayer.current?.style.setProperty(
// 'transform',
// `scale(${zoom},${zoom}) translate(${x}px,${y}px)`
// )
// }, [pageState.camera])
return rLayer return rLayer
} }

View file

@ -5,11 +5,18 @@ import type { TLBounds } from '+types'
export function usePosition(bounds: TLBounds, rotation = 0) { export function usePosition(bounds: TLBounds, rotation = 0) {
const rBounds = React.useRef<HTMLDivElement>(null) const rBounds = React.useRef<HTMLDivElement>(null)
// Update the transform
React.useLayoutEffect(() => { React.useLayoutEffect(() => {
const elm = rBounds.current! const elm = rBounds.current!
const transform = ` const transform = `
translate(calc(${bounds.minX}px - var(--tl-padding)),calc(${bounds.minY}px - var(--tl-padding))) translate3d(
calc(${bounds.minX}px - var(--tl-padding)),
calc(${bounds.minY}px - var(--tl-padding)),
0px
)
rotate(${rotation + (bounds.rotation || 0)}rad)` rotate(${rotation + (bounds.rotation || 0)}rad)`
elm.style.setProperty('transform', transform) elm.style.setProperty('transform', transform)
elm.style.setProperty('width', `calc(${Math.floor(bounds.width)}px + (var(--tl-padding) * 2))`) elm.style.setProperty('width', `calc(${Math.floor(bounds.width)}px + (var(--tl-padding) * 2))`)

View file

@ -3,31 +3,48 @@ import * as React from 'react'
import { Utils } from '+utils' import { Utils } from '+utils'
export function useResizeObserver<T extends Element>(ref: React.RefObject<T>) { export function useResizeObserver<T extends Element>(ref: React.RefObject<T>) {
const { inputs } = useTLContext() const { inputs, callbacks } = useTLContext()
const rIsMounted = React.useRef(false) const rIsMounted = React.useRef(false)
const forceUpdate = React.useReducer((x) => x + 1, 0)[1] const forceUpdate = React.useReducer((x) => x + 1, 0)[1]
const updateOffsets = React.useCallback(() => { // When the element resizes, update the bounds (stored in inputs)
// and broadcast via the onBoundsChange callback prop.
const updateBounds = React.useCallback(() => {
if (rIsMounted.current) { if (rIsMounted.current) {
const rect = ref.current?.getBoundingClientRect() const rect = ref.current?.getBoundingClientRect()
if (rect) { if (rect) {
inputs.offset = [rect.left, rect.top] inputs.bounds = {
inputs.size = [rect.width, rect.height] minX: rect.left,
maxX: rect.left + rect.width,
minY: rect.top,
maxY: rect.top + rect.height,
width: rect.width,
height: rect.height,
}
callbacks.onBoundsChange?.(inputs.bounds)
// Force an update for a second mount
forceUpdate() forceUpdate()
} }
} else {
// Skip the first mount
rIsMounted.current = true
} }
rIsMounted.current = true }, [ref, forceUpdate, inputs, callbacks.onBoundsChange])
}, [ref, forceUpdate])
React.useEffect(() => { React.useEffect(() => {
const debouncedUpdateOffsets = Utils.debounce(updateOffsets, 100) const debouncedupdateBounds = Utils.debounce(updateBounds, 100)
window.addEventListener('scroll', debouncedUpdateOffsets) window.addEventListener('scroll', debouncedupdateBounds)
window.addEventListener('resize', debouncedUpdateOffsets) window.addEventListener('resize', debouncedupdateBounds)
return () => { return () => {
window.removeEventListener('scroll', debouncedUpdateOffsets) window.removeEventListener('scroll', debouncedupdateBounds)
window.removeEventListener('resize', debouncedUpdateOffsets) window.removeEventListener('resize', debouncedupdateBounds)
} }
}, [inputs]) }, [])
React.useEffect(() => { React.useEffect(() => {
const resizeObserver = new ResizeObserver((entries) => { const resizeObserver = new ResizeObserver((entries) => {
@ -36,7 +53,7 @@ export function useResizeObserver<T extends Element>(ref: React.RefObject<T>) {
} }
if (entries[0].contentRect) { if (entries[0].contentRect) {
updateOffsets() updateBounds()
} }
}) })
@ -50,6 +67,6 @@ export function useResizeObserver<T extends Element>(ref: React.RefObject<T>) {
}, [ref, inputs]) }, [ref, inputs])
React.useEffect(() => { React.useEffect(() => {
updateOffsets() updateBounds()
}, [ref]) }, [ref])
} }

View file

@ -11,7 +11,7 @@ export function useSelection<T extends TLShape, E extends Element>(
pageState: TLPageState, pageState: TLPageState,
shapeUtils: TLShapeUtils<T, E> shapeUtils: TLShapeUtils<T, E>
) { ) {
const { rScreenBounds } = useTLContext() const { rSelectionBounds } = useTLContext()
const { selectedIds } = pageState const { selectedIds } = pageState
let bounds: TLBounds | undefined = undefined let bounds: TLBounds | undefined = undefined
@ -50,7 +50,7 @@ export function useSelection<T extends TLShape, E extends Element>(
const [minX, minY] = canvasToScreen([bounds.minX, bounds.minY], pageState.camera) const [minX, minY] = canvasToScreen([bounds.minX, bounds.minY], pageState.camera)
const [maxX, maxY] = canvasToScreen([bounds.maxX, bounds.maxY], pageState.camera) const [maxX, maxY] = canvasToScreen([bounds.maxX, bounds.maxY], pageState.camera)
rScreenBounds.current = { rSelectionBounds.current = {
minX, minX,
minY, minY,
maxX, maxX,
@ -59,7 +59,7 @@ export function useSelection<T extends TLShape, E extends Element>(
height: maxY - minY, height: maxY - minY,
} }
} else { } else {
rScreenBounds.current = null rSelectionBounds.current = null
} }
return { bounds, rotation, isLocked } return { bounds, rotation, isLocked }

View file

@ -3,7 +3,7 @@ import { Utils } from '+utils'
import { TLContext } from '+hooks' import { TLContext } from '+hooks'
export function useShapeEvents(id: string, disable = false) { export function useShapeEvents(id: string, disable = false) {
const { rPageState, rScreenBounds, callbacks, inputs } = React.useContext(TLContext) const { rPageState, rSelectionBounds, callbacks, inputs } = React.useContext(TLContext)
const onPointerDown = React.useCallback( const onPointerDown = React.useCallback(
(e: React.PointerEvent) => { (e: React.PointerEvent) => {
@ -26,8 +26,8 @@ export function useShapeEvents(id: string, disable = false) {
// treat the event as a bounding box click. Unfortunately there's no way I know to pipe // treat the event as a bounding box click. Unfortunately there's no way I know to pipe
// the event to the actual bounds background element. // the event to the actual bounds background element.
if ( if (
rScreenBounds.current && rSelectionBounds.current &&
Utils.pointInBounds(info.point, rScreenBounds.current) && Utils.pointInBounds(info.point, rSelectionBounds.current) &&
!rPageState.current.selectedIds.includes(id) !rPageState.current.selectedIds.includes(id)
) { ) {
callbacks.onPointBounds?.(inputs.pointerDown(e, 'bounds'), e) callbacks.onPointBounds?.(inputs.pointerDown(e, 'bounds'), e)

View file

@ -151,8 +151,7 @@ const tlcss = css`
height: 0; height: 0;
width: 0; width: 0;
contain: layout size; contain: layout size;
transform: scale(var(--tl-zoom), var(--tl-zoom)) transform: scale(var(--tl-zoom)) translate3d(var(--tl-camera-x), var(--tl-camera-y), 0px);
translate(var(--tl-camera-x), var(--tl-camera-y));
} }
.tl-absolute { .tl-absolute {

View file

@ -2,12 +2,13 @@ import * as React from 'react'
import type { Inputs } from '+inputs' import type { Inputs } from '+inputs'
import type { TLCallbacks, TLShape, TLBounds, TLPageState, TLShapeUtils } from '+types' import type { TLCallbacks, TLShape, TLBounds, TLPageState, TLShapeUtils } from '+types'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export interface TLContextType<T extends TLShape, E extends Element, M = any> { export interface TLContextType<T extends TLShape, E extends Element, M = any> {
id?: string id?: string
callbacks: Partial<TLCallbacks<T>> callbacks: Partial<TLCallbacks<T>>
shapeUtils: TLShapeUtils<T, E, M> shapeUtils: TLShapeUtils<T, E, M>
rPageState: React.MutableRefObject<TLPageState> rPageState: React.MutableRefObject<TLPageState>
rScreenBounds: React.MutableRefObject<TLBounds | null> rSelectionBounds: React.MutableRefObject<TLBounds | null>
inputs: Inputs inputs: Inputs
} }

View file

@ -2,17 +2,27 @@ import type React from 'react'
import type { TLKeyboardInfo, TLPointerInfo } from './types' import type { TLKeyboardInfo, TLPointerInfo } from './types'
import { Utils } from './utils' import { Utils } from './utils'
import { Vec } from '@tldraw/vec' import { Vec } from '@tldraw/vec'
import type { TLBounds } from '+index'
const DOUBLE_CLICK_DURATION = 250 const DOUBLE_CLICK_DURATION = 250
export class Inputs { export class Inputs {
pointer?: TLPointerInfo<string> pointer?: TLPointerInfo<string>
keyboard?: TLKeyboardInfo keyboard?: TLKeyboardInfo
keys: Record<string, boolean> = {} keys: Record<string, boolean> = {}
isPinching = false isPinching = false
offset = [0, 0] bounds: TLBounds = {
size = [10, 10] minX: 0,
maxX: 640,
minY: 0,
maxY: 480,
width: 640,
height: 480,
}
pointerUpTime = 0 pointerUpTime = 0
@ -41,9 +51,9 @@ export class Inputs {
const info: TLPointerInfo<T> = { const info: TLPointerInfo<T> = {
target, target,
pointerId: touch.identifier, pointerId: touch.identifier,
origin: Inputs.getPoint(touch), origin: Inputs.getPoint(touch, this.bounds),
delta: [0, 0], delta: [0, 0],
point: Inputs.getPoint(touch), point: Inputs.getPoint(touch, this.bounds),
pressure: Inputs.getPressure(touch), pressure: Inputs.getPressure(touch),
shiftKey, shiftKey,
ctrlKey, ctrlKey,
@ -64,9 +74,9 @@ export class Inputs {
const info: TLPointerInfo<T> = { const info: TLPointerInfo<T> = {
target, target,
pointerId: touch.identifier, pointerId: touch.identifier,
origin: Inputs.getPoint(touch), origin: Inputs.getPoint(touch, this.bounds),
delta: [0, 0], delta: [0, 0],
point: Inputs.getPoint(touch), point: Inputs.getPoint(touch, this.bounds),
pressure: Inputs.getPressure(touch), pressure: Inputs.getPressure(touch),
shiftKey, shiftKey,
ctrlKey, ctrlKey,
@ -88,7 +98,7 @@ export class Inputs {
const prev = this.pointer const prev = this.pointer
const point = Inputs.getPoint(touch) const point = Inputs.getPoint(touch, this.bounds)
const delta = prev?.point ? Vec.sub(point, prev.point) : [0, 0] const delta = prev?.point ? Vec.sub(point, prev.point) : [0, 0]
@ -114,7 +124,7 @@ export class Inputs {
pointerDown<T extends string>(e: PointerEvent | React.PointerEvent, target: T): TLPointerInfo<T> { pointerDown<T extends string>(e: PointerEvent | React.PointerEvent, target: T): TLPointerInfo<T> {
const { shiftKey, ctrlKey, metaKey, altKey } = e const { shiftKey, ctrlKey, metaKey, altKey } = e
const point = Inputs.getPoint(e, this.offset) const point = Inputs.getPoint(e, this.bounds)
this.activePointer = e.pointerId this.activePointer = e.pointerId
@ -142,7 +152,7 @@ export class Inputs {
): TLPointerInfo<T> { ): TLPointerInfo<T> {
const { shiftKey, ctrlKey, metaKey, altKey } = e const { shiftKey, ctrlKey, metaKey, altKey } = e
const point = Inputs.getPoint(e, this.offset) const point = Inputs.getPoint(e, this.bounds)
const info: TLPointerInfo<T> = { const info: TLPointerInfo<T> = {
target, target,
@ -167,7 +177,7 @@ export class Inputs {
const prev = this.pointer const prev = this.pointer
const point = Inputs.getPoint(e, this.offset) const point = Inputs.getPoint(e, this.bounds)
const delta = prev?.point ? Vec.sub(point, prev.point) : [0, 0] const delta = prev?.point ? Vec.sub(point, prev.point) : [0, 0]
@ -195,7 +205,7 @@ export class Inputs {
const prev = this.pointer const prev = this.pointer
const point = Inputs.getPoint(e, this.offset) const point = Inputs.getPoint(e, this.bounds)
const delta = prev?.point ? Vec.sub(point, prev.point) : [0, 0] const delta = prev?.point ? Vec.sub(point, prev.point) : [0, 0]
@ -231,7 +241,7 @@ export class Inputs {
origin: this.pointer?.origin || [0, 0], origin: this.pointer?.origin || [0, 0],
delta: [0, 0], delta: [0, 0],
pressure: 0.5, pressure: 0.5,
point: Inputs.getPoint(e, this.offset), point: Inputs.getPoint(e, this.bounds),
shiftKey, shiftKey,
ctrlKey, ctrlKey,
metaKey, metaKey,
@ -252,7 +262,7 @@ export class Inputs {
const prev = this.pointer const prev = this.pointer
const point = Inputs.getPoint(e, this.offset) const point = Inputs.getPoint(e, this.bounds)
const info: TLPointerInfo<'wheel'> = { const info: TLPointerInfo<'wheel'> = {
...prev, ...prev,
@ -330,7 +340,7 @@ export class Inputs {
target: 'pinch', target: 'pinch',
origin, origin,
delta: delta, delta: delta,
point: Vec.sub(Vec.round(point), this.offset), point: Vec.sub(Vec.round(point), [this.bounds.minX, this.bounds.minY]),
pressure: 0.5, pressure: 0.5,
shiftKey, shiftKey,
ctrlKey, ctrlKey,
@ -353,9 +363,9 @@ export class Inputs {
static getPoint( static getPoint(
e: PointerEvent | React.PointerEvent | Touch | React.Touch | WheelEvent, e: PointerEvent | React.PointerEvent | Touch | React.Touch | WheelEvent,
offset = [0, 0] bounds: TLBounds
): number[] { ): number[] {
return [+e.clientX.toFixed(2) - offset[0], +e.clientY.toFixed(2) - offset[1]] return [+e.clientX.toFixed(2) - bounds.minX, +e.clientY.toFixed(2) - bounds.minY]
} }
static getPressure(e: PointerEvent | React.PointerEvent | Touch | React.Touch | WheelEvent) { static getPressure(e: PointerEvent | React.PointerEvent | Touch | React.Touch | WheelEvent) {

View file

@ -7,13 +7,13 @@ import { Inputs } from '+inputs'
export const ContextWrapper: React.FC = ({ children }) => { export const ContextWrapper: React.FC = ({ children }) => {
useTLTheme() useTLTheme()
const rScreenBounds = React.useRef<TLBounds>(null) const rSelectionBounds = React.useRef<TLBounds>(null)
const rPageState = React.useRef<TLPageState>(mockDocument.pageState) const rPageState = React.useRef<TLPageState>(mockDocument.pageState)
const [context] = React.useState(() => ({ const [context] = React.useState(() => ({
callbacks: {}, callbacks: {},
shapeUtils: mockUtils, shapeUtils: mockUtils,
rScreenBounds, rSelectionBounds,
rPageState, rPageState,
inputs: new Inputs(), inputs: new Inputs(),
})) }))

View file

@ -205,6 +205,7 @@ export interface TLCallbacks<T extends TLShape> {
onShapeBlur: TLShapeBlurHandler<any> onShapeBlur: TLShapeBlurHandler<any>
onRenderCountChange: (ids: string[]) => void onRenderCountChange: (ids: string[]) => void
onError: (error: Error) => void onError: (error: Error) => void
onBoundsChange: (bounds: TLBounds) => void
} }
export interface TLBounds { export interface TLBounds {

View file

@ -220,7 +220,7 @@ function InnerTldraw({
onRenderCountChange={tlstate.onRenderCountChange} onRenderCountChange={tlstate.onRenderCountChange}
onShapeChange={tlstate.onShapeChange} onShapeChange={tlstate.onShapeChange}
onShapeBlur={tlstate.onShapeBlur} onShapeBlur={tlstate.onShapeBlur}
onMount={tlstate.handleMount} onBoundsChange={tlstate.updateBounds}
/> />
</ContextMenu> </ContextMenu>
<div className={menuButtons()}> <div className={menuButtons()}>

View file

@ -33,20 +33,6 @@ export class TLDR {
return Vec.sub(Vec.div(point, camera.zoom), camera.point) return Vec.sub(Vec.div(point, camera.zoom), camera.point)
} }
static getViewport(data: Data): TLBounds {
const [minX, minY] = TLDR.screenToWorld(data, [0, 0])
const [maxX, maxY] = TLDR.screenToWorld(data, [window.innerWidth, window.innerHeight])
return {
minX,
minY,
maxX,
maxY,
height: maxX - minX,
width: maxY - minY,
}
}
static getCameraZoom(zoom: number) { static getCameraZoom(zoom: number) {
return Utils.clamp(zoom, 0.1, 5) return Utils.clamp(zoom, 0.1, 5)
} }

View file

@ -119,6 +119,16 @@ export class TLDrawState extends StateManager<Data> {
selectedGroupId?: string selectedGroupId?: string
// The editor's bounding client rect
bounds: TLBounds = {
minX: 0,
minY: 0,
maxX: 640,
maxY: 480,
width: 640,
height: 480,
}
private pasteInfo = { private pasteInfo = {
center: [0, 0], center: [0, 0],
offset: [0, 0], offset: [0, 0],
@ -354,6 +364,14 @@ export class TLDrawState extends StateManager<Data> {
this.inputs = inputs this.inputs = inputs
} }
/**
* Update the bounding box when the renderer's bounds change.
* @param bounds
*/
updateBounds = (bounds: TLBounds) => {
this.bounds = { ...bounds }
}
/* -------------------------------------------------- */ /* -------------------------------------------------- */
/* Settings & UI */ /* Settings & UI */
/* -------------------------------------------------- */ /* -------------------------------------------------- */
@ -861,14 +879,7 @@ export class TLDrawState extends StateManager<Data> {
const commonBounds = Utils.getCommonBounds(shapesToPaste.map(TLDR.getBounds)) const commonBounds = Utils.getCommonBounds(shapesToPaste.map(TLDR.getBounds))
let center = Vec.round( let center = Vec.round(this.getPagePoint(point || this.centerPoint))
this.getPagePoint(
point ||
(this.inputs
? [this.inputs.size[0] / 2, this.inputs.size[1] / 2]
: [window.innerWidth / 2, window.innerHeight / 2])
)
)
if ( if (
Vec.dist(center, this.pasteInfo.center) < 2 || Vec.dist(center, this.pasteInfo.center) < 2 ||
@ -914,10 +925,7 @@ export class TLDrawState extends StateManager<Data> {
type: TLDrawShapeType.Text, type: TLDrawShapeType.Text,
parentId: this.appState.currentPageId, parentId: this.appState.currentPageId,
text: result, text: result,
point: this.getPagePoint( point: this.getPagePoint(this.centerPoint, this.currentPageId),
[window.innerWidth / 2, window.innerHeight / 2],
this.currentPageId
),
style: { ...this.appState.currentStyle }, style: { ...this.appState.currentStyle },
}) })
@ -1030,11 +1038,7 @@ export class TLDrawState extends StateManager<Data> {
* Reset the camera to the default position * Reset the camera to the default position
*/ */
resetCamera = (): this => { resetCamera = (): this => {
return this.setCamera( return this.setCamera(this.centerPoint, 1, `reset_camera`)
Vec.round([window.innerWidth / 2, window.innerHeight / 2]),
1,
`reset_camera`
)
} }
/** /**
@ -1066,7 +1070,7 @@ export class TLDrawState extends StateManager<Data> {
* @param next The new zoom level. * @param next The new zoom level.
* @param center The point to zoom towards (defaults to screen center). * @param center The point to zoom towards (defaults to screen center).
*/ */
zoomTo = (next: number, center = [window.innerWidth / 2, window.innerHeight / 2]): this => { zoomTo = (next: number, center = this.centerPoint): this => {
const { zoom, point } = this.pageState.camera const { zoom, point } = this.pageState.camera
const p0 = Vec.sub(Vec.div(center, zoom), point) const p0 = Vec.sub(Vec.div(center, zoom), point)
const p1 = Vec.sub(Vec.div(center, next), point) const p1 = Vec.sub(Vec.div(center, next), point)
@ -1102,13 +1106,13 @@ export class TLDrawState extends StateManager<Data> {
const bounds = Utils.getCommonBounds(Object.values(shapes).map(TLDR.getBounds)) const bounds = Utils.getCommonBounds(Object.values(shapes).map(TLDR.getBounds))
const zoom = TLDR.getCameraZoom( const zoom = TLDR.getCameraZoom(
window.innerWidth < window.innerHeight this.bounds.width < this.bounds.height
? (window.innerWidth - 128) / bounds.width ? (this.bounds.width - 128) / bounds.width
: (window.innerHeight - 128) / bounds.height : (this.bounds.height - 128) / bounds.height
) )
const mx = (window.innerWidth - bounds.width * zoom) / 2 / zoom const mx = (this.bounds.width - bounds.width * zoom) / 2 / zoom
const my = (window.innerHeight - bounds.height * zoom) / 2 / zoom const my = (this.bounds.height - bounds.height * zoom) / 2 / zoom
return this.setCamera( return this.setCamera(
Vec.round(Vec.add([-bounds.minX, -bounds.minY], [mx, my])), Vec.round(Vec.add([-bounds.minX, -bounds.minY], [mx, my])),
@ -1126,13 +1130,13 @@ export class TLDrawState extends StateManager<Data> {
const bounds = TLDR.getSelectedBounds(this.state) const bounds = TLDR.getSelectedBounds(this.state)
const zoom = TLDR.getCameraZoom( const zoom = TLDR.getCameraZoom(
window.innerWidth < window.innerHeight this.bounds.width < this.bounds.height
? (window.innerWidth - 128) / bounds.width ? (this.bounds.width - 128) / bounds.width
: (window.innerHeight - 128) / bounds.height : (this.bounds.height - 128) / bounds.height
) )
const mx = (window.innerWidth - bounds.width * zoom) / 2 / zoom const mx = (this.bounds.width - bounds.width * zoom) / 2 / zoom
const my = (window.innerHeight - bounds.height * zoom) / 2 / zoom const my = (this.bounds.height - bounds.height * zoom) / 2 / zoom
return this.setCamera( return this.setCamera(
Vec.round(Vec.add([-bounds.minX, -bounds.minY], [mx, my])), Vec.round(Vec.add([-bounds.minX, -bounds.minY], [mx, my])),
@ -1153,8 +1157,8 @@ export class TLDrawState extends StateManager<Data> {
const bounds = Utils.getCommonBounds(Object.values(shapes).map(TLDR.getBounds)) const bounds = Utils.getCommonBounds(Object.values(shapes).map(TLDR.getBounds))
const { zoom } = pageState.camera const { zoom } = pageState.camera
const mx = (window.innerWidth - bounds.width * zoom) / 2 / zoom const mx = (this.bounds.width - bounds.width * zoom) / 2 / zoom
const my = (window.innerHeight - bounds.height * zoom) / 2 / zoom const my = (this.bounds.height - bounds.height * zoom) / 2 / zoom
return this.setCamera( return this.setCamera(
Vec.round(Vec.add([-bounds.minX, -bounds.minY], [mx, my])), Vec.round(Vec.add([-bounds.minX, -bounds.minY], [mx, my])),
@ -2813,4 +2817,8 @@ export class TLDrawState extends StateManager<Data> {
onError = () => { onError = () => {
// TODO // TODO
} }
get centerPoint() {
return Vec.round(Utils.getBoundsCenter(this.bounds))
}
} }