kopia lustrzana https://github.com/Tldraw/Tldraw
more renaming, docs
rodzic
7230a90e59
commit
10f94eafe9
|
@ -49,16 +49,76 @@ const {
|
|||
|
||||
You can use the editor's camera options to configure the behavior of the editor's camera. There are many options available.
|
||||
|
||||
You can set the camera options using the [Editor#setCameraOptions](?) method. You can get the current camera options using the [Editor#cameraOptions](?) property.
|
||||
|
||||
### `wheelBehavior`
|
||||
|
||||
When set to `'pan'`, scrolling the mousewheel will pan the camera. When set to `'zoom'`, scrolling the mousewheel will zoom the camera.
|
||||
When set to `'pan'`, scrolling the mousewheel will pan the camera. When set to `'zoom'`, scrolling the mousewheel will zoom the camera. When set to `none`, it will have no effect.
|
||||
|
||||
### `panSpeed`
|
||||
|
||||
The speed at which the camera pans. A pan can occur when the user holds the spacebar and drags, holds the middle mouse button and drags, drags while using the hand tool, or scrolls the mousewheel. The default value is `1`. A value of `0.5` would be twice as slow as default. A value of `2` would be twice as fast.
|
||||
The speed at which the camera pans. A pan can occur when the user holds the spacebar and drags, holds the middle mouse button and drags, drags while using the hand tool, or scrolls the mousewheel. The default value is `1`. A value of `0.5` would be twice as slow as default. A value of `2` would be twice as fast. When set to `0`, the camera will not pan.
|
||||
|
||||
### `zoomSpeed`
|
||||
|
||||
The speed at which the camera zooms. A zoom can occur when the user pinches or scrolls the mouse wheel. The default value is `1`. A value of `0.5` would be twice as slow as default. A value of `2` would be twice as fast.
|
||||
The speed at which the camera zooms. A zoom can occur when the user pinches or scrolls the mouse wheel. The default value is `1`. A value of `0.5` would be twice as slow as default. A value of `2` would be twice as fast. When set to `0`, the camera will not zoom.
|
||||
|
||||
### `zoomSteps`
|
||||
|
||||
The camera's "zoom steps" are an array of discrete zoom levels that the camera will move between when using the "zoom in" or "zoom out" controls.
|
||||
|
||||
The first number in the `zoomSteps` array defines the camera's minimum zoom level. The last number in the `zoomSteps` array defines the camera's maximum zoom level.
|
||||
|
||||
If the `constraints` are provided, then the actual value for the camera's zoom will be be calculated by multiplying the value from the `zoomSteps` array with the value from the `baseZoom`. See the `baseZoom` property for more information.
|
||||
|
||||
### `isLocked`
|
||||
|
||||
Whether the camera is locked. When the camera is locked, the camera will not move.
|
||||
|
||||
### `constraints`
|
||||
|
||||
By default the camera is free to move anywhere on the infinite canvas. However, you may provide the camera with a `constraints` object that constrains the camera based on a relationship between a `bounds` (in page space) and the viewport (in screen space).
|
||||
|
||||
### `constraints.bounds`
|
||||
|
||||
A box model describing the bounds in page space.
|
||||
|
||||
### `constraints.padding`
|
||||
|
||||
An object with padding to apply to the `x` and `y` dimensions of the viewport. The padding is in screen space.
|
||||
|
||||
### `constraints.origin`
|
||||
|
||||
An object with an origin for the `x` and `y` dimensions. Depending on the `behavior`, the origin may be used to position the bounds within the viewport.
|
||||
|
||||
For example, when the `behavior` is `fixed` and the `origin.x` is `0`, the bounds will be placed with its left side touching the left side of the viewport. When `origin.x` is `1` the bounds will be placed with its right side touching the right side of the viewport. By default the origin for each dimension is .5. This places the bounds in the center of the viewport.
|
||||
|
||||
### `constraints.initialZoom`
|
||||
|
||||
The `initialZoom` option defines the camera's initial zoom level and what the zoom should be when when the camera is reset. The zoom it produces is based on the value provided:
|
||||
|
||||
| Value | Description |
|
||||
| --------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| 'default' | 100%. |
|
||||
| 'fit-x' | The zoom at which the constraint's `bounds.width` exactly fits within the viewport. |
|
||||
| 'fit-y' | The zoom at which the constraint's `bounds.height` exactly fits within the viewport. |
|
||||
| 'fit-min' | The zoom at which the _smaller_ of the constraint's `bounds.width` or `bounds.height` exactly fits within the viewport. |
|
||||
| 'fit-max' | The zoom at which the _larger_ of the constraint's `bounds.width` or `bounds.height` exactly fits within the viewport. |
|
||||
|
||||
### `constraints.baseZoom`
|
||||
|
||||
The `baseZoom` property defines the base property for the camera's zoom steps. It accepts the same values as `initialZoom`.
|
||||
|
||||
When `constraints` are provided, then the actual value for the camera's zoom will be be calculated by multiplying the value from the `zoomSteps` array with the value from the `baseZoom`.
|
||||
|
||||
For example, if the `baseZoom` is set to `default`, then a zoom step of 2 will be 200%. However, if the `baseZoom` is set to `fit-x`, then a zoom step value of 2 will be twice the zoom level at which the bounds width exactly fits within the viewport.
|
||||
|
||||
### `constraints.behavior`
|
||||
|
||||
The `behavior` property defines which logic should be used when calculating the bounds position.
|
||||
|
||||
| Value | Description |
|
||||
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 'free' | The bounds may be placed anywhere relative to the viewport. This is the default "infinite canvas" experience. |
|
||||
| 'inside' | The bounds must stay entirely within the viewport. |
|
||||
| 'outside' | The bounds may partially leave the viewport but must never leave it completely. |
|
||||
| 'fixed' | The bounds are placed in the viewport at a fixed location according to the `'origin'`. |
|
||||
| 'contain' | When the zoom is below the "fit zoom" for an axis, the bounds use the `'fixed'` behavior; when above, the bounds use the `inside` behavior. |
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { default as React, useEffect } from 'react'
|
||||
import { Editor, TLPageId, clamp, debounce, react, useEditor } from 'tldraw'
|
||||
import { Editor, TLPageId, Vec, clamp, debounce, react, useEditor } from 'tldraw'
|
||||
|
||||
const PARAMS = {
|
||||
// deprecated
|
||||
|
@ -68,19 +68,13 @@ export function useUrlState(onChangeUrl: (params: UrlStateParams) => void) {
|
|||
const viewport = viewportFromString(newViewportRaw)
|
||||
const { x, y, w, h } = viewport
|
||||
const { w: sw, h: sh } = editor.getViewportScreenBounds()
|
||||
const fitZoom = editor.getCameraFitZoom()
|
||||
const initialZoom = editor.getInitialZoom()
|
||||
const { zoomSteps } = editor.getCameraOptions()
|
||||
const zoomMin = zoomSteps[0]
|
||||
const zoomMax = zoomSteps[zoomSteps.length - 1]
|
||||
|
||||
const zoom = clamp(Math.min(sw / w, sh / h), zoomMin * fitZoom, zoomMax * fitZoom)
|
||||
|
||||
const zoom = clamp(Math.min(sw / w, sh / h), zoomMin * initialZoom, zoomMax * initialZoom)
|
||||
editor.setCamera(
|
||||
{
|
||||
x: -x + (sw - w * zoom) / 2 / zoom,
|
||||
y: -y + (sh - h * zoom) / 2 / zoom,
|
||||
z: zoom,
|
||||
},
|
||||
new Vec(-x + (sw - w * zoom) / 2 / zoom, -y + (sh - h * zoom) / 2 / zoom, zoom),
|
||||
{ immediate: true }
|
||||
)
|
||||
} catch (err) {
|
||||
|
|
|
@ -18,8 +18,8 @@ const CAMERA_OPTIONS: TLCameraOptions = {
|
|||
zoomSpeed: 1,
|
||||
zoomSteps: [0.1, 0.25, 0.5, 1, 2, 4, 8],
|
||||
constraints: {
|
||||
defaultZoom: 'fit-max',
|
||||
zoomBehavior: 'fit',
|
||||
initialZoom: 'fit-max',
|
||||
baseZoom: 'fit-max',
|
||||
bounds: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
|
@ -283,15 +283,15 @@ const CameraOptionsControlPanel = track(() => {
|
|||
</select>
|
||||
{constraints ? (
|
||||
<>
|
||||
<label htmlFor="defaultzoom">Default Zoom</label>
|
||||
<label htmlFor="initialZoom">Initial Zoom</label>
|
||||
<select
|
||||
name="defaultzoom"
|
||||
value={constraints.defaultZoom}
|
||||
name="initialZoom"
|
||||
value={constraints.initialZoom}
|
||||
onChange={(e) => {
|
||||
updateOptions({
|
||||
constraints: {
|
||||
...constraints,
|
||||
defaultZoom: e.target.value as any,
|
||||
initialZoom: e.target.value as any,
|
||||
},
|
||||
})
|
||||
}}
|
||||
|
@ -302,20 +302,23 @@ const CameraOptionsControlPanel = track(() => {
|
|||
<option>fit-y</option>
|
||||
<option>default</option>
|
||||
</select>
|
||||
<label htmlFor="fit">Zoom Behavior</label>
|
||||
<label htmlFor="zoomBehavior">Base Zoom</label>
|
||||
<select
|
||||
name="zoomBehavior"
|
||||
value={constraints.zoomBehavior}
|
||||
value={constraints.baseZoom}
|
||||
onChange={(e) => {
|
||||
updateOptions({
|
||||
constraints: {
|
||||
...constraints,
|
||||
zoomBehavior: e.target.value as any,
|
||||
baseZoom: e.target.value as any,
|
||||
},
|
||||
})
|
||||
}}
|
||||
>
|
||||
<option>fit</option>
|
||||
<option>fit-min</option>
|
||||
<option>fit-max</option>
|
||||
<option>fit-x</option>
|
||||
<option>fit-y</option>
|
||||
<option>default</option>
|
||||
</select>
|
||||
<label htmlFor="originX">Origin X</label>
|
||||
|
@ -392,9 +395,9 @@ const CameraOptionsControlPanel = track(() => {
|
|||
})
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="fitx">Fit X</label>
|
||||
<label htmlFor="behaviorX">Behavior X</label>
|
||||
<select
|
||||
name="fitx"
|
||||
name="behaviorX"
|
||||
value={(constraints.behavior as { x: any; y: any }).x}
|
||||
onChange={(e) => {
|
||||
setCameraOptions({
|
||||
|
@ -414,9 +417,9 @@ const CameraOptionsControlPanel = track(() => {
|
|||
<option>outside</option>
|
||||
<option>lock</option>
|
||||
</select>
|
||||
<label htmlFor="fity">Fit Y</label>
|
||||
<label htmlFor="behaviorY">Behavior Y</label>
|
||||
<select
|
||||
name="fity"
|
||||
name="behaviorY"
|
||||
value={(constraints.behavior as { x: any; y: any }).y}
|
||||
onChange={(e) => {
|
||||
setCameraOptions({
|
||||
|
|
|
@ -133,8 +133,8 @@ export function ImageAnnotationEditor({
|
|||
editor.setCameraOptions(
|
||||
{
|
||||
constraints: {
|
||||
defaultZoom: 'fit-max',
|
||||
zoomBehavior: 'default',
|
||||
initialZoom: 'fit-max',
|
||||
baseZoom: 'default',
|
||||
bounds: { w: image.width, h: image.height, x: 0, y: 0 },
|
||||
padding: { x: 32, y: 64 },
|
||||
origin: { x: 0.5, y: 0.5 },
|
||||
|
|
|
@ -692,10 +692,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
getAsset(asset: TLAsset | TLAssetId): TLAsset | undefined;
|
||||
getAssetForExternalContent(info: TLExternalAssetContent): Promise<TLAsset | undefined>;
|
||||
getAssets(): (TLBookmarkAsset | TLImageAsset | TLVideoAsset)[];
|
||||
getBaseZoom(): number;
|
||||
getCamera(): TLCamera;
|
||||
getCameraFitZoom(opts?: {
|
||||
reset: boolean;
|
||||
}): number;
|
||||
getCameraOptions(): TLCameraOptions;
|
||||
getCameraState(): "idle" | "moving";
|
||||
getCanRedo(): boolean;
|
||||
|
@ -734,6 +732,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
getHoveredShape(): TLShape | undefined;
|
||||
getHoveredShapeId(): null | TLShapeId;
|
||||
getInitialMetaForShape(_shape: TLShape): JsonObject;
|
||||
getInitialZoom(): number;
|
||||
getInstanceState(): TLInstance;
|
||||
getIsMenuOpen(): boolean;
|
||||
getOnlySelectedShape(): null | TLShape;
|
||||
|
@ -2011,15 +2010,15 @@ export type TLCameraMoveOptions = Partial<{
|
|||
export type TLCameraOptions = {
|
||||
wheelBehavior: 'none' | 'pan' | 'zoom';
|
||||
constraints?: {
|
||||
behavior: 'contain' | 'fixed' | 'inside' | 'outside' | {
|
||||
x: 'contain' | 'fixed' | 'inside' | 'outside';
|
||||
y: 'contain' | 'fixed' | 'inside' | 'outside';
|
||||
behavior: 'contain' | 'fixed' | 'free' | 'inside' | 'outside' | {
|
||||
x: 'contain' | 'fixed' | 'free' | 'inside' | 'outside';
|
||||
y: 'contain' | 'fixed' | 'free' | 'inside' | 'outside';
|
||||
};
|
||||
zoomBehavior: 'default' | 'fit';
|
||||
bounds: BoxModel;
|
||||
baseZoom: 'default' | 'fit-max' | 'fit-min' | 'fit-x' | 'fit-y';
|
||||
initialZoom: 'default' | 'fit-max' | 'fit-min' | 'fit-x' | 'fit-y';
|
||||
origin: VecLike;
|
||||
padding: VecLike;
|
||||
defaultZoom: 'default' | 'fit-max' | 'fit-min' | 'fit-x' | 'fit-y';
|
||||
};
|
||||
panSpeed: number;
|
||||
zoomSpeed: number;
|
||||
|
|
|
@ -9838,6 +9838,37 @@
|
|||
"isAbstract": false,
|
||||
"name": "getAssets"
|
||||
},
|
||||
{
|
||||
"kind": "Method",
|
||||
"canonicalReference": "@tldraw/editor!Editor#getBaseZoom:member(1)",
|
||||
"docComment": "/**\n * Get the camera's base level for calculating actual zoom levels based on the zoom steps.\n *\n * @example\n * ```ts\n * editor.getBaseZoom()\n * ```\n *\n * @public\n */\n",
|
||||
"excerptTokens": [
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "getBaseZoom(): "
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "number"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": ";"
|
||||
}
|
||||
],
|
||||
"isStatic": false,
|
||||
"returnTypeTokenRange": {
|
||||
"startIndex": 1,
|
||||
"endIndex": 2
|
||||
},
|
||||
"releaseTag": "Public",
|
||||
"isProtected": false,
|
||||
"overloadIndex": 1,
|
||||
"parameters": [],
|
||||
"isOptional": false,
|
||||
"isAbstract": false,
|
||||
"name": "getBaseZoom"
|
||||
},
|
||||
{
|
||||
"kind": "Method",
|
||||
"canonicalReference": "@tldraw/editor!Editor#getCamera:member(1)",
|
||||
|
@ -9874,54 +9905,6 @@
|
|||
"isAbstract": false,
|
||||
"name": "getCamera"
|
||||
},
|
||||
{
|
||||
"kind": "Method",
|
||||
"canonicalReference": "@tldraw/editor!Editor#getCameraFitZoom:member(1)",
|
||||
"docComment": "/**\n * Get the zoom level that would fit the camera to the current constraints.\n *\n * @example\n * ```ts\n * editor.getCameraFitZoom()\n * ```\n *\n * @public\n */\n",
|
||||
"excerptTokens": [
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "getCameraFitZoom(opts?: "
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "{\n reset: boolean;\n }"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "): "
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "number"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": ";"
|
||||
}
|
||||
],
|
||||
"isStatic": false,
|
||||
"returnTypeTokenRange": {
|
||||
"startIndex": 3,
|
||||
"endIndex": 4
|
||||
},
|
||||
"releaseTag": "Public",
|
||||
"isProtected": false,
|
||||
"overloadIndex": 1,
|
||||
"parameters": [
|
||||
{
|
||||
"parameterName": "opts",
|
||||
"parameterTypeTokenRange": {
|
||||
"startIndex": 1,
|
||||
"endIndex": 2
|
||||
},
|
||||
"isOptional": true
|
||||
}
|
||||
],
|
||||
"isOptional": false,
|
||||
"isAbstract": false,
|
||||
"name": "getCameraFitZoom"
|
||||
},
|
||||
{
|
||||
"kind": "Method",
|
||||
"canonicalReference": "@tldraw/editor!Editor#getCameraOptions:member(1)",
|
||||
|
@ -11266,6 +11249,37 @@
|
|||
"isAbstract": false,
|
||||
"name": "getInitialMetaForShape"
|
||||
},
|
||||
{
|
||||
"kind": "Method",
|
||||
"canonicalReference": "@tldraw/editor!Editor#getInitialZoom:member(1)",
|
||||
"docComment": "/**\n * Get the camera's initial or reset zoom level.\n *\n * @example\n * ```ts\n * editor.getInitialZoom()\n * ```\n *\n * @public\n */\n",
|
||||
"excerptTokens": [
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "getInitialZoom(): "
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "number"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": ";"
|
||||
}
|
||||
],
|
||||
"isStatic": false,
|
||||
"returnTypeTokenRange": {
|
||||
"startIndex": 1,
|
||||
"endIndex": 2
|
||||
},
|
||||
"releaseTag": "Public",
|
||||
"isProtected": false,
|
||||
"overloadIndex": 1,
|
||||
"parameters": [],
|
||||
"isOptional": false,
|
||||
"isAbstract": false,
|
||||
"name": "getInitialZoom"
|
||||
},
|
||||
{
|
||||
"kind": "Method",
|
||||
"canonicalReference": "@tldraw/editor!Editor#getInstanceState:member(1)",
|
||||
|
@ -37064,7 +37078,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "{\n wheelBehavior: 'none' | 'pan' | 'zoom';\n constraints?: {\n behavior: 'contain' | 'fixed' | 'inside' | 'outside' | {\n x: 'contain' | 'fixed' | 'inside' | 'outside';\n y: 'contain' | 'fixed' | 'inside' | 'outside';\n };\n zoomBehavior: 'default' | 'fit';\n bounds: "
|
||||
"text": "{\n wheelBehavior: 'none' | 'pan' | 'zoom';\n constraints?: {\n behavior: 'contain' | 'fixed' | 'free' | 'inside' | 'outside' | {\n x: 'contain' | 'fixed' | 'free' | 'inside' | 'outside';\n y: 'contain' | 'fixed' | 'free' | 'inside' | 'outside';\n };\n bounds: "
|
||||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
|
@ -37073,7 +37087,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": ";\n origin: "
|
||||
"text": ";\n baseZoom: 'default' | 'fit-max' | 'fit-min' | 'fit-x' | 'fit-y';\n initialZoom: 'default' | 'fit-max' | 'fit-min' | 'fit-x' | 'fit-y';\n origin: "
|
||||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
|
@ -37091,7 +37105,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": ";\n defaultZoom: 'default' | 'fit-max' | 'fit-min' | 'fit-x' | 'fit-y';\n };\n panSpeed: number;\n zoomSpeed: number;\n zoomSteps: number[];\n isLocked: boolean;\n}"
|
||||
"text": ";\n };\n panSpeed: number;\n zoomSpeed: number;\n zoomSteps: number[];\n isLocked: boolean;\n}"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
|
|
|
@ -2064,36 +2064,25 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the zoom level that would fit the camera to the current constraints.
|
||||
* Get the camera's initial or reset zoom level.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* editor.getCameraFitZoom()
|
||||
* editor.getInitialZoom()
|
||||
* ```
|
||||
*
|
||||
* @public */
|
||||
getCameraFitZoom(opts = {} as { reset: boolean }) {
|
||||
getInitialZoom() {
|
||||
const cameraOptions = this.getCameraOptions()
|
||||
if (
|
||||
// If no camera constraints are provided, the default zoom is 100%
|
||||
!cameraOptions.constraints ||
|
||||
// When defaultZoom is default, the default zoom is 100%
|
||||
cameraOptions.constraints.defaultZoom === 'default' ||
|
||||
// When zoomBehavior is default, we ignore the default zoom and use 100% as the fit zoom
|
||||
(!opts.reset && cameraOptions.constraints.zoomBehavior === 'default')
|
||||
) {
|
||||
return 1
|
||||
}
|
||||
// If no camera constraints are provided, the default zoom is 100%
|
||||
if (!cameraOptions.constraints) return 1
|
||||
|
||||
const { padding } = cameraOptions.constraints
|
||||
const vsb = this.getViewportScreenBounds()
|
||||
const py = Math.min(padding.y, vsb.w / 2)
|
||||
const px = Math.min(padding.x, vsb.h / 2)
|
||||
const bounds = Box.From(cameraOptions.constraints.bounds)
|
||||
const zx = (vsb.w - px * 2) / bounds.w
|
||||
const zy = (vsb.h - py * 2) / bounds.h
|
||||
// When defaultZoom is default, the default zoom is 100%
|
||||
if (cameraOptions.constraints.initialZoom === 'default') return 1
|
||||
|
||||
switch (cameraOptions.constraints.defaultZoom) {
|
||||
const { zx, zy } = getCameraFitXFitY(this, cameraOptions)
|
||||
|
||||
switch (cameraOptions.constraints.initialZoom) {
|
||||
case 'fit-min': {
|
||||
return Math.max(zx, zy)
|
||||
}
|
||||
|
@ -2106,9 +2095,46 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
case 'fit-y': {
|
||||
return zy
|
||||
}
|
||||
// none is accounted-for above
|
||||
default: {
|
||||
throw exhaustiveSwitchError(cameraOptions.constraints.defaultZoom)
|
||||
throw exhaustiveSwitchError(cameraOptions.constraints.initialZoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the camera's base level for calculating actual zoom levels based on the zoom steps.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* editor.getBaseZoom()
|
||||
* ```
|
||||
*
|
||||
* @public */
|
||||
getBaseZoom() {
|
||||
const cameraOptions = this.getCameraOptions()
|
||||
// If no camera constraints are provided, the default zoom is 100%
|
||||
if (!cameraOptions.constraints) return 1
|
||||
|
||||
// When defaultZoom is default, the default zoom is 100%
|
||||
if (cameraOptions.constraints.baseZoom === 'default') return 1
|
||||
|
||||
const { zx, zy } = getCameraFitXFitY(this, cameraOptions)
|
||||
|
||||
switch (cameraOptions.constraints.baseZoom) {
|
||||
case 'fit-min': {
|
||||
return Math.max(zx, zy)
|
||||
}
|
||||
case 'fit-max': {
|
||||
return Math.min(zx, zy)
|
||||
}
|
||||
case 'fit-x': {
|
||||
return zx
|
||||
}
|
||||
case 'fit-y': {
|
||||
return zy
|
||||
}
|
||||
default: {
|
||||
throw exhaustiveSwitchError(cameraOptions.constraints.baseZoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2189,32 +2215,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
const zx = (vsb.w - px * 2) / bounds.w
|
||||
const zy = (vsb.h - py * 2) / bounds.h
|
||||
|
||||
let fitZoom = 1
|
||||
|
||||
switch (cameraOptions.constraints.defaultZoom) {
|
||||
case 'fit-min': {
|
||||
fitZoom = Math.max(zx, zy)
|
||||
break
|
||||
}
|
||||
case 'fit-max': {
|
||||
fitZoom = Math.min(zx, zy)
|
||||
break
|
||||
}
|
||||
case 'fit-x': {
|
||||
fitZoom = zx
|
||||
break
|
||||
}
|
||||
case 'fit-y': {
|
||||
fitZoom = zy
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const maxZ = zoomMax * fitZoom
|
||||
const minZ = zoomMin * fitZoom
|
||||
const baseZoom = this.getBaseZoom()
|
||||
const maxZ = zoomMax * baseZoom
|
||||
const minZ = zoomMin * baseZoom
|
||||
|
||||
if (opts?.reset) {
|
||||
z = fitZoom
|
||||
z = this.getInitialZoom()
|
||||
}
|
||||
|
||||
if (z < minZ || z > maxZ) {
|
||||
|
@ -2277,6 +2283,13 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
x = clamp(x, px / z - bounds.w, (vsb.w - px) / z)
|
||||
break
|
||||
}
|
||||
case 'free': {
|
||||
// noop, use whatever x is provided
|
||||
break
|
||||
}
|
||||
default: {
|
||||
throw exhaustiveSwitchError(behaviorX)
|
||||
}
|
||||
}
|
||||
|
||||
// y axis
|
||||
|
@ -2300,19 +2313,22 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
y = clamp(y, py / z - bounds.h, (vsb.h - py) / z)
|
||||
break
|
||||
}
|
||||
case 'free': {
|
||||
// noop, use whatever x is provided
|
||||
break
|
||||
}
|
||||
default: {
|
||||
throw exhaustiveSwitchError(behaviorY)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// constrain the zoom, preserving the center
|
||||
if (z > zoomMax || z < zoomMin) {
|
||||
const { x: cx, y: cy, z: cz } = currentCamera
|
||||
const cxA = -cx + vsb.w / cz / 2
|
||||
const cyA = -cy + vsb.h / cz / 2
|
||||
z = clamp(z, zoomMin, zoomMax)
|
||||
const cxB = -cx + vsb.w / z / 2
|
||||
const cyB = -cy + vsb.h / z / 2
|
||||
x = cx + cxB - cxA
|
||||
y = cy + cyB - cyA
|
||||
x = cx + (-cx + vsb.w / z / 2) - (-cx + vsb.w / cz / 2)
|
||||
y = cy + (-cy + vsb.h / z / 2) - (-cy + vsb.h / cz / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2499,9 +2515,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
if (constraints) {
|
||||
// For non-infinite fit, we'll set the camera to the natural zoom level...
|
||||
// unless it's already there, in which case we'll set zoom to 100%
|
||||
const fitZoom = this.getCameraFitZoom({ reset: true })
|
||||
if (cz !== fitZoom) {
|
||||
z = fitZoom
|
||||
const initialZoom = this.getInitialZoom()
|
||||
if (cz !== initialZoom) {
|
||||
z = initialZoom
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2534,11 +2550,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
const { zoomSteps } = this.getCameraOptions()
|
||||
if (zoomSteps !== null && zoomSteps.length > 1) {
|
||||
const fitZoom = this.getCameraFitZoom()
|
||||
let zoom = last(zoomSteps)! * fitZoom
|
||||
const baseZoom = this.getBaseZoom()
|
||||
let zoom = last(zoomSteps)! * baseZoom
|
||||
for (let i = 1; i < zoomSteps.length; i++) {
|
||||
const z1 = zoomSteps[i - 1] * fitZoom
|
||||
const z2 = zoomSteps[i] * fitZoom
|
||||
const z1 = zoomSteps[i - 1] * baseZoom
|
||||
const z2 = zoomSteps[i] * baseZoom
|
||||
if (z2 - cz <= (z2 - z1) / 2) continue
|
||||
zoom = z2
|
||||
break
|
||||
|
@ -2576,22 +2592,22 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
const { zoomSteps } = this.getCameraOptions()
|
||||
if (zoomSteps !== null && zoomSteps.length > 1) {
|
||||
const fitZoom = this.getCameraFitZoom()
|
||||
const baseZoom = this.getBaseZoom()
|
||||
const { x: cx, y: cy, z: cz } = this.getCamera()
|
||||
let zoom = zoomSteps[0] * fitZoom
|
||||
let zoom = zoomSteps[0] * baseZoom
|
||||
for (let i = zoomSteps.length - 2; i > 0; i--) {
|
||||
const z1 = zoomSteps[i - 1] * fitZoom
|
||||
const z2 = zoomSteps[i] * fitZoom
|
||||
const z1 = zoomSteps[i - 1] * baseZoom
|
||||
const z2 = zoomSteps[i] * baseZoom
|
||||
if (z2 - cz >= (z2 - z1) / 2) continue
|
||||
zoom = z1
|
||||
break
|
||||
}
|
||||
this.setCamera(
|
||||
{
|
||||
x: cx + (point.x / zoom - point.x) - (point.x / cz - point.x),
|
||||
y: cy + (point.y / zoom - point.y) - (point.y / cz - point.y),
|
||||
z: zoom,
|
||||
},
|
||||
new Vec(
|
||||
cx + (point.x / zoom - point.x) - (point.x / cz - point.x),
|
||||
cy + (point.y / zoom - point.y) - (point.y / cz - point.y),
|
||||
zoom
|
||||
),
|
||||
opts
|
||||
)
|
||||
}
|
||||
|
@ -2709,7 +2725,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
const inset = opts?.inset ?? Math.min(256, viewportScreenBounds.width * 0.28)
|
||||
|
||||
const fitZoom = this.getCameraFitZoom()
|
||||
const baseZoom = this.getBaseZoom()
|
||||
const { zoomSteps } = this.getCameraOptions()
|
||||
const zoomMin = zoomSteps[0]
|
||||
const zoomMax = last(zoomSteps)!
|
||||
|
@ -2719,8 +2735,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
(viewportScreenBounds.width - inset) / bounds.w,
|
||||
(viewportScreenBounds.height - inset) / bounds.h
|
||||
),
|
||||
zoomMin * fitZoom,
|
||||
zoomMax * fitZoom
|
||||
zoomMin * baseZoom,
|
||||
zoomMax * baseZoom
|
||||
)
|
||||
|
||||
if (opts?.targetZoom !== undefined) {
|
||||
|
@ -3012,6 +3028,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
|
||||
this._animateToViewport(targetViewportPage, opts)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
|
@ -3317,11 +3334,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
? Math.min(width / desiredWidth, height / desiredHeight)
|
||||
: height / desiredHeight
|
||||
|
||||
const fitZoom = this.getCameraFitZoom()
|
||||
const baseZoom = this.getBaseZoom()
|
||||
const { zoomSteps } = this.getCameraOptions()
|
||||
const zoomMin = zoomSteps[0]
|
||||
const zoomMax = last(zoomSteps)!
|
||||
const targetZoom = clamp(this.getCamera().z * ratio, zoomMin * fitZoom, zoomMax * fitZoom)
|
||||
const targetZoom = clamp(this.getCamera().z * ratio, zoomMin * baseZoom, zoomMax * baseZoom)
|
||||
const targetWidth = this.getViewportScreenBounds().w / targetZoom
|
||||
const targetHeight = this.getViewportScreenBounds().h / targetZoom
|
||||
|
||||
|
@ -8940,44 +8957,44 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
} else {
|
||||
const { panSpeed, zoomSpeed, wheelBehavior } = cameraOptions
|
||||
|
||||
if (wheelBehavior === 'none') return
|
||||
|
||||
// Stop any camera animation
|
||||
this.stopCameraAnimation()
|
||||
// Stop following any following user
|
||||
if (instanceState.followingUserId) {
|
||||
this.stopFollowingUser()
|
||||
}
|
||||
|
||||
const { x: cx, y: cy, z: cz } = camera
|
||||
const { x: dx, y: dy, z: dz = 0 } = info.delta
|
||||
|
||||
// If the camera behavior is "zoom" and the ctrl key is presssed, then pan;
|
||||
// If the camera behavior is "pan" and the ctrl key is not pressed, then zoom
|
||||
const behavior =
|
||||
wheelBehavior === 'pan' ? (inputs.ctrlKey ? 'zoom' : 'pan') : wheelBehavior
|
||||
|
||||
switch (behavior) {
|
||||
case 'zoom': {
|
||||
// Zoom in on current screen point using the wheel delta
|
||||
const { x, y } = this.inputs.currentScreenPoint
|
||||
const zoom = cz + (dz ?? 0) * zoomSpeed * cz
|
||||
this._setCamera(
|
||||
new Vec(
|
||||
cx + (x / zoom - x) - (x / cz - x),
|
||||
cy + (y / zoom - y) - (y / cz - y),
|
||||
zoom
|
||||
),
|
||||
{ immediate: true }
|
||||
)
|
||||
return
|
||||
if (wheelBehavior !== 'none') {
|
||||
// Stop any camera animation
|
||||
this.stopCameraAnimation()
|
||||
// Stop following any following user
|
||||
if (instanceState.followingUserId) {
|
||||
this.stopFollowingUser()
|
||||
}
|
||||
case 'pan': {
|
||||
// Pan the camera based on the wheel delta
|
||||
this._setCamera(new Vec(cx + (dx * panSpeed) / cz, cy + (dy * panSpeed) / cz, cz), {
|
||||
immediate: true,
|
||||
})
|
||||
return
|
||||
|
||||
const { x: cx, y: cy, z: cz } = camera
|
||||
const { x: dx, y: dy, z: dz = 0 } = info.delta
|
||||
|
||||
// If the camera behavior is "zoom" and the ctrl key is presssed, then pan;
|
||||
// If the camera behavior is "pan" and the ctrl key is not pressed, then zoom
|
||||
const behavior =
|
||||
wheelBehavior === 'pan' ? (inputs.ctrlKey ? 'zoom' : 'pan') : wheelBehavior
|
||||
|
||||
switch (behavior) {
|
||||
case 'zoom': {
|
||||
// Zoom in on current screen point using the wheel delta
|
||||
const { x, y } = this.inputs.currentScreenPoint
|
||||
const zoom = cz + (dz ?? 0) * zoomSpeed * cz
|
||||
this._setCamera(
|
||||
new Vec(
|
||||
cx + (x / zoom - x) - (x / cz - x),
|
||||
cy + (y / zoom - y) - (y / cz - y),
|
||||
zoom
|
||||
),
|
||||
{ immediate: true }
|
||||
)
|
||||
return
|
||||
}
|
||||
case 'pan': {
|
||||
// Pan the camera based on the wheel delta
|
||||
this._setCamera(new Vec(cx + (dx * panSpeed) / cz, cy + (dy * panSpeed) / cz, cz), {
|
||||
immediate: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9267,3 +9284,15 @@ function pushShapeWithDescendants(editor: Editor, id: TLShapeId, result: TLShape
|
|||
pushShapeWithDescendants(editor, childIds[i], result)
|
||||
}
|
||||
}
|
||||
|
||||
function getCameraFitXFitY(editor: Editor, cameraOptions: TLCameraOptions) {
|
||||
if (!cameraOptions.constraints) throw Error('Should have constraints here')
|
||||
const {
|
||||
padding: { x: px, y: py },
|
||||
} = cameraOptions.constraints
|
||||
const vsb = editor.getViewportScreenBounds()
|
||||
const bounds = Box.From(cameraOptions.constraints.bounds)
|
||||
const zx = (vsb.w - px * 2) / bounds.w
|
||||
const zy = (vsb.h - py * 2) / bounds.h
|
||||
return { zx, zy }
|
||||
}
|
||||
|
|
|
@ -48,25 +48,26 @@ export type TLCameraOptions = {
|
|||
isLocked: boolean
|
||||
/** The camera constraints */
|
||||
constraints?: {
|
||||
/** Which dimension to fit when the camera is reset. */
|
||||
defaultZoom: 'fit-min' | 'fit-max' | 'fit-x' | 'fit-y' | 'default'
|
||||
/** The behavior for the zoom. When 'fit', the steps will be a multiplier of the default zoom. */
|
||||
zoomBehavior: 'fit' | 'default'
|
||||
/** The behavior for the constraints on the x axis. */
|
||||
behavior:
|
||||
| 'contain'
|
||||
| 'inside'
|
||||
| 'outside'
|
||||
| 'fixed'
|
||||
| {
|
||||
x: 'contain' | 'inside' | 'outside' | 'fixed'
|
||||
y: 'contain' | 'inside' | 'outside' | 'fixed'
|
||||
}
|
||||
/** The bounds (in page space) of the constrained space */
|
||||
bounds: BoxModel
|
||||
/** The padding inside of the viewport (in screen space) */
|
||||
padding: VecLike
|
||||
/** The origin for placement. Used to position the bounds within the viewport when an axis is fixed or contained and zoom is below the axis fit. */
|
||||
origin: VecLike
|
||||
/** The camera's initial zoom, used also when the camera is reset. */
|
||||
initialZoom: 'fit-min' | 'fit-max' | 'fit-x' | 'fit-y' | 'default'
|
||||
/** The camera's base for its zoom steps. */
|
||||
baseZoom: 'fit-min' | 'fit-max' | 'fit-x' | 'fit-y' | 'default'
|
||||
/** The behavior for the constraints on the x axis. */
|
||||
behavior:
|
||||
| 'free'
|
||||
| 'contain'
|
||||
| 'inside'
|
||||
| 'outside'
|
||||
| 'fixed'
|
||||
| {
|
||||
x: 'contain' | 'inside' | 'outside' | 'fixed' | 'free'
|
||||
y: 'contain' | 'inside' | 'outside' | 'fixed' | 'free'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
import { TLCameraOptions } from '@tldraw/editor'
|
||||
import { TestEditor } from '../TestEditor'
|
||||
|
||||
let editor: TestEditor
|
||||
|
||||
beforeEach(() => {
|
||||
editor = new TestEditor()
|
||||
})
|
||||
|
||||
describe('getBaseZoom', () => {
|
||||
it('gets initial zoom with default options', () => {
|
||||
expect(editor.getBaseZoom()).toBe(1)
|
||||
})
|
||||
|
||||
it('gets initial zoom based on constraints', () => {
|
||||
const vsb = editor.getViewportScreenBounds()
|
||||
let cameraOptions: TLCameraOptions
|
||||
|
||||
cameraOptions = editor.getCameraOptions()
|
||||
editor.setCameraOptions({
|
||||
...cameraOptions,
|
||||
constraints: {
|
||||
bounds: { x: 0, y: 0, w: vsb.w * 2, h: vsb.h * 4 },
|
||||
padding: { x: 0, y: 0 },
|
||||
origin: { x: 0.5, y: 0.5 },
|
||||
initialZoom: 'default',
|
||||
baseZoom: 'default',
|
||||
behavior: 'free',
|
||||
},
|
||||
})
|
||||
|
||||
expect(editor.getBaseZoom()).toBe(1)
|
||||
|
||||
cameraOptions = editor.getCameraOptions()
|
||||
editor.setCameraOptions({
|
||||
...cameraOptions,
|
||||
constraints: {
|
||||
...(cameraOptions.constraints as any),
|
||||
baseZoom: 'fit-x',
|
||||
},
|
||||
})
|
||||
|
||||
expect(editor.getBaseZoom()).toBe(0.5)
|
||||
|
||||
cameraOptions = editor.getCameraOptions()
|
||||
editor.setCameraOptions({
|
||||
...cameraOptions,
|
||||
constraints: {
|
||||
...(cameraOptions.constraints as any),
|
||||
baseZoom: 'fit-y',
|
||||
},
|
||||
})
|
||||
|
||||
expect(editor.getBaseZoom()).toBe(0.25)
|
||||
|
||||
cameraOptions = editor.getCameraOptions()
|
||||
editor.setCameraOptions({
|
||||
...cameraOptions,
|
||||
constraints: {
|
||||
...(cameraOptions.constraints as any),
|
||||
baseZoom: 'fit-min',
|
||||
},
|
||||
})
|
||||
|
||||
expect(editor.getBaseZoom()).toBe(0.5)
|
||||
|
||||
cameraOptions = editor.getCameraOptions()
|
||||
editor.setCameraOptions({
|
||||
...cameraOptions,
|
||||
constraints: {
|
||||
...(cameraOptions.constraints as any),
|
||||
baseZoom: 'fit-max',
|
||||
},
|
||||
})
|
||||
|
||||
expect(editor.getBaseZoom()).toBe(0.25)
|
||||
|
||||
cameraOptions = editor.getCameraOptions()
|
||||
editor.setCameraOptions({
|
||||
...cameraOptions,
|
||||
constraints: {
|
||||
...(cameraOptions.constraints as any),
|
||||
baseZoom: 'default',
|
||||
},
|
||||
})
|
||||
|
||||
expect(editor.getBaseZoom()).toBe(1)
|
||||
})
|
||||
})
|
|
@ -0,0 +1,89 @@
|
|||
import { TLCameraOptions } from '@tldraw/editor'
|
||||
import { TestEditor } from '../TestEditor'
|
||||
|
||||
let editor: TestEditor
|
||||
|
||||
beforeEach(() => {
|
||||
editor = new TestEditor()
|
||||
})
|
||||
|
||||
describe('getInitialZoom', () => {
|
||||
it('gets initial zoom with default options', () => {
|
||||
expect(editor.getInitialZoom()).toBe(1)
|
||||
})
|
||||
|
||||
it('gets initial zoom based on constraints', () => {
|
||||
const vsb = editor.getViewportScreenBounds()
|
||||
let cameraOptions: TLCameraOptions
|
||||
|
||||
cameraOptions = editor.getCameraOptions()
|
||||
editor.setCameraOptions({
|
||||
...cameraOptions,
|
||||
constraints: {
|
||||
bounds: { x: 0, y: 0, w: vsb.w * 2, h: vsb.h * 4 },
|
||||
padding: { x: 0, y: 0 },
|
||||
origin: { x: 0.5, y: 0.5 },
|
||||
initialZoom: 'default',
|
||||
baseZoom: 'default',
|
||||
behavior: 'free',
|
||||
},
|
||||
})
|
||||
|
||||
expect(editor.getInitialZoom()).toBe(1)
|
||||
|
||||
cameraOptions = editor.getCameraOptions()
|
||||
editor.setCameraOptions({
|
||||
...cameraOptions,
|
||||
constraints: {
|
||||
...(cameraOptions.constraints as any),
|
||||
initialZoom: 'fit-x',
|
||||
},
|
||||
})
|
||||
|
||||
expect(editor.getInitialZoom()).toBe(0.5)
|
||||
|
||||
cameraOptions = editor.getCameraOptions()
|
||||
editor.setCameraOptions({
|
||||
...cameraOptions,
|
||||
constraints: {
|
||||
...(cameraOptions.constraints as any),
|
||||
initialZoom: 'fit-y',
|
||||
},
|
||||
})
|
||||
|
||||
expect(editor.getInitialZoom()).toBe(0.25)
|
||||
|
||||
cameraOptions = editor.getCameraOptions()
|
||||
editor.setCameraOptions({
|
||||
...cameraOptions,
|
||||
constraints: {
|
||||
...(cameraOptions.constraints as any),
|
||||
initialZoom: 'fit-min',
|
||||
},
|
||||
})
|
||||
|
||||
expect(editor.getInitialZoom()).toBe(0.5)
|
||||
|
||||
cameraOptions = editor.getCameraOptions()
|
||||
editor.setCameraOptions({
|
||||
...cameraOptions,
|
||||
constraints: {
|
||||
...(cameraOptions.constraints as any),
|
||||
initialZoom: 'fit-max',
|
||||
},
|
||||
})
|
||||
|
||||
expect(editor.getInitialZoom()).toBe(0.25)
|
||||
|
||||
cameraOptions = editor.getCameraOptions()
|
||||
editor.setCameraOptions({
|
||||
...cameraOptions,
|
||||
constraints: {
|
||||
...(cameraOptions.constraints as any),
|
||||
initialZoom: 'default',
|
||||
},
|
||||
})
|
||||
|
||||
expect(editor.getInitialZoom()).toBe(1)
|
||||
})
|
||||
})
|
|
@ -1,4 +1,3 @@
|
|||
import { DEFAULT_CAMERA_OPTIONS } from '@tldraw/editor'
|
||||
import { TestEditor } from '../TestEditor'
|
||||
|
||||
let editor: TestEditor
|
||||
|
@ -8,7 +7,7 @@ beforeEach(() => {
|
|||
})
|
||||
|
||||
it('zooms out and in by increments', () => {
|
||||
const cameraOptions = DEFAULT_CAMERA_OPTIONS
|
||||
const cameraOptions = editor.getCameraOptions()
|
||||
|
||||
// Starts at 1
|
||||
expect(editor.getZoomLevel()).toBe(cameraOptions.zoomSteps[3])
|
||||
|
@ -30,3 +29,87 @@ it('does not zoom out when camera is frozen', () => {
|
|||
editor.zoomOut()
|
||||
expect(editor.getCamera()).toMatchObject({ x: 0, y: 0, z: 1 })
|
||||
})
|
||||
|
||||
it('zooms out and in by increments when the camera options have constraints but no base zoom', () => {
|
||||
const cameraOptions = editor.getCameraOptions()
|
||||
editor.setCameraOptions({
|
||||
...cameraOptions,
|
||||
constraints: {
|
||||
bounds: { x: 0, y: 0, w: 1600, h: 900 },
|
||||
padding: { x: 0, y: 0 },
|
||||
origin: { x: 0.5, y: 0.5 },
|
||||
initialZoom: 'default',
|
||||
baseZoom: 'default',
|
||||
behavior: 'free',
|
||||
},
|
||||
})
|
||||
// Starts at 1
|
||||
expect(editor.getZoomLevel()).toBe(cameraOptions.zoomSteps[3])
|
||||
editor.zoomOut()
|
||||
expect(editor.getZoomLevel()).toBe(cameraOptions.zoomSteps[2])
|
||||
editor.zoomOut()
|
||||
expect(editor.getZoomLevel()).toBe(cameraOptions.zoomSteps[1])
|
||||
editor.zoomOut()
|
||||
expect(editor.getZoomLevel()).toBe(cameraOptions.zoomSteps[0])
|
||||
// does not zoom out past min
|
||||
editor.zoomOut()
|
||||
expect(editor.getZoomLevel()).toBe(cameraOptions.zoomSteps[0])
|
||||
})
|
||||
|
||||
it('zooms out and in by increments when the camera options have constraints and a base zoom', () => {
|
||||
const cameraOptions = editor.getCameraOptions()
|
||||
const vsb = editor.getViewportScreenBounds()
|
||||
editor.setCameraOptions({
|
||||
...cameraOptions,
|
||||
constraints: {
|
||||
bounds: { x: 0, y: 0, w: vsb.w * 2, h: vsb.h * 4 },
|
||||
padding: { x: 0, y: 0 },
|
||||
origin: { x: 0.5, y: 0.5 },
|
||||
initialZoom: 'fit-x',
|
||||
baseZoom: 'fit-x',
|
||||
behavior: 'free',
|
||||
},
|
||||
})
|
||||
// And reset the zoom to its initial value
|
||||
editor.resetZoom()
|
||||
|
||||
expect(editor.getInitialZoom()).toBe(0.5) // fitting the x axis
|
||||
// Starts at 1
|
||||
expect(editor.getZoomLevel()).toBe(cameraOptions.zoomSteps[3] * 0.5)
|
||||
editor.zoomOut()
|
||||
expect(editor.getZoomLevel()).toBe(cameraOptions.zoomSteps[2] * 0.5)
|
||||
editor.zoomOut()
|
||||
expect(editor.getZoomLevel()).toBe(cameraOptions.zoomSteps[1] * 0.5)
|
||||
editor.zoomOut()
|
||||
expect(editor.getZoomLevel()).toBe(cameraOptions.zoomSteps[0] * 0.5)
|
||||
// does not zoom out past min
|
||||
editor.zoomOut()
|
||||
expect(editor.getZoomLevel()).toBe(cameraOptions.zoomSteps[0] * 0.5)
|
||||
|
||||
editor.setCameraOptions({
|
||||
...cameraOptions,
|
||||
constraints: {
|
||||
bounds: { x: 0, y: 0, w: vsb.w * 2, h: vsb.h * 4 },
|
||||
padding: { x: 0, y: 0 },
|
||||
origin: { x: 0.5, y: 0.5 },
|
||||
initialZoom: 'fit-y',
|
||||
baseZoom: 'fit-y',
|
||||
behavior: 'free',
|
||||
},
|
||||
})
|
||||
// And reset the zoom to its initial value
|
||||
editor.resetZoom()
|
||||
|
||||
expect(editor.getInitialZoom()).toBe(0.25) // fitting the y axis
|
||||
// Starts at 1
|
||||
expect(editor.getZoomLevel()).toBe(cameraOptions.zoomSteps[3] * 0.25)
|
||||
editor.zoomOut()
|
||||
expect(editor.getZoomLevel()).toBe(cameraOptions.zoomSteps[2] * 0.25)
|
||||
editor.zoomOut()
|
||||
expect(editor.getZoomLevel()).toBe(cameraOptions.zoomSteps[1] * 0.25)
|
||||
editor.zoomOut()
|
||||
expect(editor.getZoomLevel()).toBe(cameraOptions.zoomSteps[0] * 0.25)
|
||||
// does not zoom out past min
|
||||
editor.zoomOut()
|
||||
expect(editor.getZoomLevel()).toBe(cameraOptions.zoomSteps[0] * 0.25)
|
||||
})
|
||||
|
|
Ładowanie…
Reference in New Issue