Skip to content

Commit

Permalink
[Resize content][3/n] Sync horizontal pan and zoom states (#108)
Browse files Browse the repository at this point in the history
Summary
---

Syncs the state of all 3 horizontal pan and zoom views.

Fixes the known issue from the first PR in this stack.

Test Plan
---

* `yarn lint`
* `yarn flow`: no errors in changed code
* `yarn test`: no new tests; all existing ones passing
* `yarn start`: manual test: panning and zooming on any horizontal pan
  and zoom view also pans and zooms the others.
  • Loading branch information
taneliang authored Aug 4, 2020
1 parent fcc9af4 commit 62a6796
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 61 deletions.
45 changes: 34 additions & 11 deletions src/CanvasPage.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow

import type {Point} from './layout';
import type {Point, HorizontalPanAndZoomViewOnChangeCallback} from './layout';

import {copy} from 'clipboard-js';
import React, {
Expand Down Expand Up @@ -93,6 +93,17 @@ const copySummary = (data, measure) => {
// zoomTo(startTime, stopTime);
// };

const syncedHorizontalPanAndZoomViews: HorizontalPanAndZoomView[] = [];
const syncAllHorizontalPanAndZoomViewStates: HorizontalPanAndZoomViewOnChangeCallback = (
newState,
view,
) => {
syncedHorizontalPanAndZoomViews.forEach(
syncedView =>
view !== syncedView && syncedView.setPanAndZoomState(newState),
);
};

type AutoSizedCanvasProps = {|
data: ReactProfilerData,
height: number,
Expand All @@ -118,6 +129,12 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
const surface = surfaceRef.current;
const defaultFrame = {origin: zeroPoint, size: {width, height}};

// Clear synced views
syncedHorizontalPanAndZoomViews.splice(
0,
syncedHorizontalPanAndZoomViews.length,
);

// Top content

const axisMarkersView = new TimeAxisMarkersView(
Expand All @@ -137,12 +154,14 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
topContentStack.addSubview(axisMarkersView);
topContentStack.addSubview(reactEventsView);

const topContentZoomWrapper = new HorizontalPanAndZoomView(
const topContentHorizontalPanAndZoomView = new HorizontalPanAndZoomView(
surface,
defaultFrame,
topContentStack,
data.duration,
syncAllHorizontalPanAndZoomViewStates,
);
syncedHorizontalPanAndZoomViews.push(topContentHorizontalPanAndZoomView);

// Resizable content

Expand All @@ -152,17 +171,19 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
data,
);
reactMeasuresViewRef.current = reactMeasuresView;
const vScrollWrappedReactMeasuresView = new VerticalScrollView(
const reactMeasuresVerticalScrollView = new VerticalScrollView(
surface,
defaultFrame,
reactMeasuresView,
);
const hScrollWrappedReactMeasuresView = new HorizontalPanAndZoomView(
const reactMeasuresHorizontalPanAndZoomView = new HorizontalPanAndZoomView(
surface,
defaultFrame,
vScrollWrappedReactMeasuresView,
reactMeasuresVerticalScrollView,
data.duration,
syncAllHorizontalPanAndZoomViewStates,
);
syncedHorizontalPanAndZoomViews.push(reactMeasuresHorizontalPanAndZoomView);

const flamechartView = new FlamechartView(
surface,
Expand All @@ -171,23 +192,25 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
data.duration,
);
flamechartViewRef.current = flamechartView;
const vScrollWrappedFlamechartView = new VerticalScrollView(
const flamechartVerticalScrollView = new VerticalScrollView(
surface,
defaultFrame,
flamechartView,
);
const hScrollWrappedFlamechartView = new HorizontalPanAndZoomView(
const flamechartHorizontalPanAndZoomView = new HorizontalPanAndZoomView(
surface,
defaultFrame,
vScrollWrappedFlamechartView,
flamechartVerticalScrollView,
data.duration,
syncAllHorizontalPanAndZoomViewStates,
);
syncedHorizontalPanAndZoomViews.push(flamechartHorizontalPanAndZoomView);

const resizableContentStack = new ResizableSplitView(
surface,
defaultFrame,
hScrollWrappedReactMeasuresView,
hScrollWrappedFlamechartView,
reactMeasuresHorizontalPanAndZoomView,
flamechartHorizontalPanAndZoomView,
);

const rootView = new View(
Expand All @@ -198,7 +221,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
lastViewTakesUpRemainingSpaceLayout,
),
);
rootView.addSubview(topContentZoomWrapper);
rootView.addSubview(topContentHorizontalPanAndZoomView);
rootView.addSubview(resizableContentStack);

surfaceRef.current.rootView = rootView;
Expand Down
75 changes: 45 additions & 30 deletions src/layout/HorizontalPanAndZoomView.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,16 @@ import {
MOVE_WHEEL_DELTA_THRESHOLD,
} from '../canvas/constants'; // TODO: Remove external dependency

type HorizontalPanAndZoomState = {|
type HorizontalPanAndZoomState = $ReadOnly<{|
/** Horizontal offset; positive in the left direction */
offsetX: number,
zoomLevel: number,
|};
|}>;

export type HorizontalPanAndZoomViewOnChangeCallback = (
state: HorizontalPanAndZoomState,
view: HorizontalPanAndZoomView,
) => void;

function panAndZoomStatesAreEqual(
state1: HorizontalPanAndZoomState,
Expand Down Expand Up @@ -62,34 +67,58 @@ export class HorizontalPanAndZoomView extends View {

_isPanning = false;

_stateDeriver: (
state: HorizontalPanAndZoomState,
) => HorizontalPanAndZoomState = state => state;

_onStateChange: (state: HorizontalPanAndZoomState) => void = () => {};
_onStateChange: HorizontalPanAndZoomViewOnChangeCallback = () => {};

constructor(
surface: Surface,
frame: Rect,
contentView: View,
intrinsicContentWidth: number,
stateDeriver?: (
state: HorizontalPanAndZoomState,
) => HorizontalPanAndZoomState,
onStateChange?: (state: HorizontalPanAndZoomState) => void,
onStateChange?: HorizontalPanAndZoomViewOnChangeCallback,
) {
super(surface, frame);
this.addSubview(contentView);
this._intrinsicContentWidth = intrinsicContentWidth;
if (stateDeriver) this._stateDeriver = stateDeriver;
if (onStateChange) this._onStateChange = onStateChange;
}

setFrame(newFrame: Rect) {
super.setFrame(newFrame);

// Revalidate panAndZoomState
this._updateState(this._panAndZoomState);
this._setStateAndInformCallbacksIfChanged(this._panAndZoomState);
}

setPanAndZoomState(proposedState: HorizontalPanAndZoomState) {
this._setPanAndZoomState(proposedState);
}

/**
* Just sets pan and zoom state. Use `_setStateAndInformCallbacksIfChanged`
* if this view's callbacks should also be called.
*
* @returns Whether state was changed
* @private
*/
_setPanAndZoomState(proposedState: HorizontalPanAndZoomState): boolean {
const clampedState = this._clampedProposedState(proposedState);
if (panAndZoomStatesAreEqual(clampedState, this._panAndZoomState)) {
return false;
}
this._panAndZoomState = clampedState;
this.setNeedsDisplay();
return true;
}

/**
* @private
*/
_setStateAndInformCallbacksIfChanged(
proposedState: HorizontalPanAndZoomState,
) {
if (this._setPanAndZoomState(proposedState)) {
this._onStateChange(this._panAndZoomState, this);
}
}

desiredSize() {
Expand Down Expand Up @@ -135,7 +164,7 @@ export class HorizontalPanAndZoomView extends View {
}
const {offsetX} = this._panAndZoomState;
const {movementX} = interaction.payload.event;
this._updateState({
this._setStateAndInformCallbacksIfChanged({
...this._panAndZoomState,
offsetX: offsetX + movementX,
});
Expand Down Expand Up @@ -166,7 +195,7 @@ export class HorizontalPanAndZoomView extends View {
return;
}

this._updateState({
this._setStateAndInformCallbacksIfChanged({
...this._panAndZoomState,
offsetX: this._panAndZoomState.offsetX - deltaX,
});
Expand Down Expand Up @@ -212,7 +241,7 @@ export class HorizontalPanAndZoomView extends View {
offsetX: location.x - newMouseXInFrame,
});

this._updateState(offsetAdjustedState);
this._setStateAndInformCallbacksIfChanged(offsetAdjustedState);
}

handleInteraction(interaction: Interaction) {
Expand All @@ -237,20 +266,6 @@ export class HorizontalPanAndZoomView extends View {
}
}

/**
* @private
*/
_updateState(proposedState: HorizontalPanAndZoomState) {
const clampedState = this._stateDeriver(
this._clampedProposedState(proposedState),
);
if (!panAndZoomStatesAreEqual(clampedState, this._panAndZoomState)) {
this._panAndZoomState = clampedState;
this._onStateChange(this._panAndZoomState);
this.setNeedsDisplay();
}
}

/**
* @private
*/
Expand Down
24 changes: 4 additions & 20 deletions src/layout/VerticalScrollView.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import {View} from './View';
import {rectContainsPoint} from './geometry';
import {MOVE_WHEEL_DELTA_THRESHOLD} from '../canvas/constants'; // TODO: Remove external dependency

type VerticalScrollState = {|
type VerticalScrollState = $ReadOnly<{|
offsetY: number,
|};
|}>;

function scrollStatesAreEqual(
state1: VerticalScrollState,
Expand All @@ -42,22 +42,9 @@ export class VerticalScrollView extends View {

_isPanning = false;

_stateDeriver: (state: VerticalScrollState) => VerticalScrollState = state =>
state;

_onStateChange: (state: VerticalScrollState) => void = () => {};

constructor(
surface: Surface,
frame: Rect,
contentView: View,
stateDeriver?: (state: VerticalScrollState) => VerticalScrollState,
onStateChange?: (state: VerticalScrollState) => void,
) {
constructor(surface: Surface, frame: Rect, contentView: View) {
super(surface, frame);
this.addSubview(contentView);
if (stateDeriver) this._stateDeriver = stateDeriver;
if (onStateChange) this._onStateChange = onStateChange;
}

setFrame(newFrame: Rect) {
Expand Down Expand Up @@ -172,12 +159,9 @@ export class VerticalScrollView extends View {
* @private
*/
_updateState(proposedState: VerticalScrollState) {
const clampedState = this._stateDeriver(
this._clampedProposedState(proposedState),
);
const clampedState = this._clampedProposedState(proposedState);
if (!scrollStatesAreEqual(clampedState, this._scrollState)) {
this._scrollState = clampedState;
this._onStateChange(this._scrollState);
this.setNeedsDisplay();
}
}
Expand Down

0 comments on commit 62a6796

Please sign in to comment.