Skip to content

Commit

Permalink
added access to viewport properties, enabled more customization of co…
Browse files Browse the repository at this point in the history
…ntrol modifier handling
  • Loading branch information
daign committed Feb 11, 2024
1 parent ebc3a05 commit 4397916
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 66 deletions.
33 changes: 20 additions & 13 deletions lib/application/interactiveViewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,12 @@ export class InteractiveViewport extends Viewport {

this.viewportHandle = handle;

let viewScaleSnapshot: number;
let transformationSnapshot: Matrix3;

handle.beginning = (): boolean => {
// Save snapshots of center and scale values at drag start.
this.viewCenter.snap();
viewScaleSnapshot = this.viewScale;
this.viewScale.snap();
transformationSnapshot = this.transformation.inverseTransformMatrix.clone();
return true;
};
Expand All @@ -101,7 +100,8 @@ export class InteractiveViewport extends Viewport {
if (
startPosition1 !== undefined && startPosition2 !== undefined &&
tempPosition1 !== undefined && tempPosition2 !== undefined &&
this.viewCenter.snapshot !== undefined
this.viewCenter.snapshot !== undefined &&
this.viewScale.snapshot !== undefined
) {
// Calculate the distances between the touch points at start and current position.
let distanceStart = startPosition2.clone().sub( startPosition1 ).length();
Expand All @@ -113,8 +113,9 @@ export class InteractiveViewport extends Viewport {

// Zooming by relative distance change between touch points.
const zoomFactor = distanceTemp / distanceStart;
const newScale = viewScaleSnapshot * zoomFactor;
this.viewScale = Math.max( 0.01, Math.min( 1000, newScale ) );
const newScale = this.viewScale.snapshot.x * zoomFactor;
// Set silent so that updateViewport can be called once at the end.
this.viewScale.setSilent( newScale );

const transformedTempPosition1 = tempPosition1.clone()
.transform( transformationSnapshot );
Expand All @@ -126,11 +127,15 @@ export class InteractiveViewport extends Viewport {
.sub( transformedTempPosition1 )
.multiplyScalar( 1 / zoomFactor )
.add( transformedstartPosition1 );
this.viewCenter.copy( newCenter );
} else if ( delta1 !== undefined ) {
this.viewCenter.copySilent( newCenter );
} else if (
delta1 !== undefined &&
this.viewCenter.snapshot !== undefined
) {
// When there is one position only, then pan the viewport.
const drag = delta1.clone().multiplyScalar( -1 / this.viewScale );
this.viewCenter.drag( drag );
const drag = delta1.clone().multiplyScalar( -1 / this.viewScale.x );
const newCenter = this.viewCenter.snapshot.clone().add( drag );
this.viewCenter.copySilent( newCenter );
}

this.updateViewport();
Expand All @@ -148,13 +153,15 @@ export class InteractiveViewport extends Viewport {
const sign = Math.sign( handle.scroll.y );
let factor = Math.pow( 1.1, -sign );

const oldScale = this.viewScale;
this.viewScale = Math.max( 0.01, Math.min( 1000, oldScale * factor ) );
factor = this.viewScale / oldScale;
const oldScale = this.viewScale.x;
this.viewScale.setSilent( oldScale * factor );
factor = this.viewScale.x / oldScale;

const mousePosition = handle.scrollPosition.clone();
mousePosition.transform( this.transformation.inverseTransformMatrix );
this.viewCenter.sub( mousePosition ).multiplyScalar( 1 / factor ).add( mousePosition );
const newCenter = this.viewCenter.clone().sub( mousePosition )
.multiplyScalar( 1 / factor ).add( mousePosition );
this.viewCenter.copySilent( newCenter );

this.updateViewport();
this.application.redraw();
Expand Down
55 changes: 46 additions & 9 deletions lib/application/viewport.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Vector2 } from '@daign/math';
import { Box2, Value, Vector2 } from '@daign/math';
import { NativeScaleTransform, NativeTranslateTransform } from '@daign/2d-pipeline';

import { Group } from '../basic-elements/group';
Expand All @@ -19,10 +19,19 @@ export class Viewport extends Group {
private translateTransform: NativeTranslateTransform = new NativeTranslateTransform();

// Content coordinates at the center of the viewport.
protected viewCenter: Vector2 = new Vector2();
public viewCenter: Vector2 = new Vector2();

// Scaling of the viewport content.
protected viewScale: number = 1;
public viewScale: Value = new Value( 1 );

// Minimum scale.
public scaleMin: number = 0.001;

// Maximum scale.
public scaleMax: number = 1000;

// Area limiting view center movements.
public viewCenterLimit: Box2 = new Box2();

/**
* Constructor.
Expand All @@ -38,14 +47,22 @@ export class Viewport extends Group {
this.context = context;
this.application = application;

// Set center of target's drawing space as center.
this.viewCenter.copy( context.size ).multiplyScalar( 0.5 );
// Initialize with unlimited view center limit.
this.viewCenterLimit.makeUnlimited();

// Add the transformations that transform the viewport.
this.scaleTransform.scaling.set( 1, 1 );
this.transformation.push( this.translateTransform );
this.transformation.push( this.scaleTransform );
this.transformation.push( this.decenteringTransform );

// Automatically update viewport when center or scale changes.
this.viewCenter.subscribeToChanges( (): void => {
this.updateViewport();
} );
this.viewScale.subscribeToChanges( (): void => {
this.updateViewport();
} );
}

/**
Expand All @@ -60,10 +77,24 @@ export class Viewport extends Group {
}

const scaling = this.context.size.clone().divide( contentBox.size );
// Scaling should be the same in both directions.
this.viewScale = Math.min( scaling.x, scaling.y );
// Scaling is the same in both directions.
// Set silent so that updateViewport can be called once at the end.
this.viewScale.setSilent( Math.min( scaling.x, scaling.y ) );

this.viewCenter.copy( contentBox.min ).add( contentBox.size.clone().multiplyScalar( 0.5 ) );
const newCenter = contentBox.min.clone().add( contentBox.size.clone().multiplyScalar( 0.5 ) );
this.viewCenter.copySilent( newCenter );

this.updateViewport();
}

/**
* Set view center and scale so that the coordinates translate one-to-one to context coordinates.
*/
public fitToContextSize(): void {
// Set center of target's drawing space as center.
const newCenter = this.context.size.clone().multiplyScalar( 0.5 );
this.viewCenter.copySilent( newCenter );
this.viewScale.setSilent( 1 );

this.updateViewport();
}
Expand All @@ -72,10 +103,16 @@ export class Viewport extends Group {
* Calculate and apply the transformations that result from the view center and scale properties.
*/
protected updateViewport(): void {
// Apply limits to center and scale.
const limitedCenter = this.viewCenter.clone().clampInBox( this.viewCenterLimit );
this.viewCenter.copySilent( limitedCenter );
const limitedScale = this.viewScale.clone().clamp( this.scaleMin, this.scaleMax );
this.viewScale.setSilent( limitedScale.x );

// Move the center of the view to (0,0) to keep it unaffected from the scaling.
const decentering = new Vector2( -this.viewCenter.x, -this.viewCenter.y );

const scaling = new Vector2( this.viewScale, this.viewScale );
const scaling = new Vector2( this.viewScale.x, this.viewScale.x );
const translation = this.context.size.clone().multiplyScalar( 0.5 );

this.decenteringTransform.translation.copy( decentering );
Expand Down
24 changes: 23 additions & 1 deletion lib/control-elements/controlObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export abstract class ControlObject extends Group {
public controlGuides: IControlGuide[] = [];

// Array of custom shapes to be used for control points.
public controlPointShapes: ( StyledGraphicNode | null )[] = [];
public controlPointShapes: ( StyledGraphicNode | null | undefined )[] = [];

// Array of button objects.
public buttons: ButtonObject[] = [];
Expand Down Expand Up @@ -91,4 +91,26 @@ export abstract class ControlObject extends Group {

return group;
}

/**
* Get a deep copy of the control points.
* The ControlPoint uses this method for the ControlModifiers. The method will be overwritten by
* ControlObjects that extend the Vector2 class with additional properties.
* @returns A deep copy of the control points.
*/
public getDeepCopyOfPoints(): Vector2[] {
return this.points.cloneDeep().elements;
}

/**
* Copy updated coordinates back to the control points of this control.
* The ControlPoint uses this method for the ControlModifiers. The method will be overwritten by
* ControlObjects that extend the Vector2 class with additional properties.
* @param updatedPoints - The new coordinates to apply.
*/
public writeUpdatesToPoints( updatedPoints: Vector2[] ): void {
this.points.iterate( ( element: Vector2, index: number ): void => {
element.copy( updatedPoints[ index ] );
} );
}
}
6 changes: 2 additions & 4 deletions lib/control-elements/controlPoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export class ControlPoint extends Group {
const calculatedPosition = this.center.clone().transform( presentationNode.projectViewToNode );

// Create copy of all points of the control object and set the calculated position.
let updatedPoints = this.controlObject.points.cloneDeep().elements;
let updatedPoints = this.controlObject.getDeepCopyOfPoints();
updatedPoints[ this.controlIndex ].copy( calculatedPosition );

// If a control modifier exists, then modify the points through it.
Expand All @@ -106,9 +106,7 @@ export class ControlPoint extends Group {
}

// Copy all coordinates back to the control object.
this.controlObject.points.iterate( ( element: Vector2, index: number ): void => {
element.copy( updatedPoints[ index ] );
} );
this.controlObject.writeUpdatesToPoints( updatedPoints );

this.calculateOffset();

Expand Down
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@daign/2d-graphics",
"version": "1.1.12",
"version": "1.1.13",
"description": "Two dimensional graphics library that implements the daign-2d-pipeline.",
"keywords": [
"graphics",
Expand Down Expand Up @@ -30,23 +30,23 @@
"devDependencies": {
"@types/chai": "^4.3.11",
"@types/mocha": "^10.0.6",
"@types/sinon": "^17.0.2",
"chai": "^4.3.10",
"mocha": "^10.2.0",
"@types/sinon": "^17.0.3",
"chai": "^4.4.1",
"mocha": "^10.3.0",
"nyc": "^15.1.0",
"sinon": "^17.0.1",
"source-map-support": "^0.5.21",
"ts-node": "^10.9.2",
"tslint": "^6.1.3",
"tslint-no-unused-expression-chai": "^0.1.4",
"typescript": "5.0.2",
"typescript": "~5.1.6",
"@daign/mock-dom": "^1.1.0"
},
"dependencies": {
"@daign/handle": "^1.1.0",
"@daign/math": "^1.1.3",
"@daign/handle": "^1.1.1",
"@daign/math": "^1.1.4",
"@daign/observable": "^1.1.3",
"@daign/2d-pipeline": "^1.1.2",
"@daign/2d-pipeline": "^1.1.3",
"@daign/style-sheets": "^1.1.1"
},
"nyc": {
Expand Down

0 comments on commit 4397916

Please sign in to comment.