Skip to content

Commit eaa8c2c

Browse files
author
Brian Vaughn
committed
STASHING
1 parent 89979f5 commit eaa8c2c

5 files changed

Lines changed: 328 additions & 6 deletions

File tree

packages/react-devtools-scheduling-profiler/src/CanvasPage.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
BackgroundColorView,
3535
HorizontalPanAndZoomView,
3636
ResizableView,
37+
VerticalScrollOverflowView,
3738
Surface,
3839
VerticalScrollView,
3940
View,
@@ -293,7 +294,13 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
293294
// If subviews are less than the available height, fill remaining height with a solid color.
294295
rootView.addSubview(new BackgroundColorView(surface, defaultFrame));
295296

296-
surfaceRef.current.rootView = rootView;
297+
const rootViewWithVerticalScroll = new VerticalScrollOverflowView(
298+
surface,
299+
defaultFrame,
300+
rootView,
301+
);
302+
303+
surfaceRef.current.rootView = rootViewWithVerticalScroll;
297304
}, [data]);
298305

299306
useLayoutEffect(() => {
Lines changed: 316 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,316 @@
1-
// TODO Vertically stack views (via verticallyStackedLayout).
2-
// If stacked views are taller than the available height, a vertical scrollbar will be shown on the side,
3-
// and width will be adjusted to subtract the width of the scrollbar.
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
import type {Interaction} from './useCanvasInteraction';
11+
import type {Rect} from './geometry';
12+
import type {Surface, ViewRefs} from './Surface';
13+
import type {
14+
ClickInteraction,
15+
MouseDownInteraction,
16+
MouseMoveInteraction,
17+
MouseUpInteraction,
18+
} from './useCanvasInteraction';
19+
20+
import {
21+
intersectionOfRects,
22+
rectContainsPoint,
23+
rectIntersectsRect,
24+
} from './geometry';
25+
import {View} from './View';
26+
import {BORDER_SIZE, COLORS} from '../content-views/constants';
27+
28+
const SCROLL_BAR_SIZE = 14;
29+
30+
const HIDDEN_RECT = {
31+
origin: {
32+
x: 0,
33+
y: 0,
34+
},
35+
size: {
36+
width: 0,
37+
height: 0,
38+
},
39+
};
40+
41+
// TODO How do we handle resizing
42+
export class VerticalScrollOverflowView extends View {
43+
_contentView: View;
44+
_isScrolling: boolean = false;
45+
_scrollOffset: number = 0;
46+
_scrollBarView: VerticalScrollBarView;
47+
48+
constructor(surface: Surface, frame: Rect, contentView: View) {
49+
super(surface, frame);
50+
51+
this._contentView = contentView;
52+
this._scrollBarView = new VerticalScrollBarView(surface, frame, this);
53+
54+
this.addSubview(contentView);
55+
this.addSubview(this._scrollBarView);
56+
}
57+
58+
setScrollOffset(value: number) {
59+
this._scrollOffset = value;
60+
61+
const proposedFrame = {
62+
...this._contentView.frame,
63+
origin: {
64+
...this._contentView.frame.origin,
65+
y: value,
66+
}
67+
};
68+
69+
// TODO How do we pass this down to the sub views?
70+
// It doesn't currently seem to be working.
71+
// this._contentView.setVisibleArea(visibleArea);
72+
73+
this._contentView.subviews.forEach((subview, subviewIndex) => {
74+
if (rectIntersectsRect(proposedFrame, subview.frame)) {
75+
subview.setFrame(intersectionOfRects(proposedFrame, subview.frame));
76+
} else {
77+
subview.setFrame(HIDDEN_RECT);
78+
}
79+
});
80+
81+
this.setNeedsDisplay();
82+
}
83+
84+
layoutSubviews() {
85+
super.layoutSubviews();
86+
87+
const {frame} = this;
88+
const {x, y} = frame.origin;
89+
const {width, height} = frame.size;
90+
91+
const contentHeight = this._contentView.desiredSize().height;
92+
const shouldScroll = contentHeight > height;
93+
94+
const scrollBarView = this._scrollBarView;
95+
96+
this._contentView.setVisibleArea({
97+
origin: {
98+
x,
99+
y: y + this._scrollOffset,
100+
},
101+
size: {
102+
width: shouldScroll ? width - SCROLL_BAR_SIZE : width,
103+
height,
104+
},
105+
});
106+
107+
if (shouldScroll) {
108+
const scrollBarX = x + width - SCROLL_BAR_SIZE;
109+
110+
const proposedScrollBarFrame = {
111+
origin: {
112+
x: scrollBarX,
113+
y,
114+
},
115+
size: {
116+
width: SCROLL_BAR_SIZE,
117+
height,
118+
},
119+
};
120+
121+
scrollBarView.setFrame(proposedScrollBarFrame);
122+
scrollBarView.setContentHeight(contentHeight);
123+
scrollBarView.setShouldScroll(true);
124+
} else {
125+
scrollBarView.setShouldScroll(false);
126+
}
127+
128+
this.setNeedsDisplay();
129+
}
130+
}
131+
132+
export class VerticalScrollBarView extends View {
133+
_contentHeight: number = 0;
134+
_isScrolling: boolean = false;
135+
_scrollBarRect: Rect = HIDDEN_RECT;
136+
_scrollThumbRect: Rect = HIDDEN_RECT;
137+
_shouldScroll: boolean = false;
138+
_verticalScrollOverflowView: VerticalScrollOverflowView;
139+
140+
constructor(
141+
surface: Surface,
142+
frame: Rect,
143+
verticalScrollOverflowView: VerticalScrollOverflowView,
144+
) {
145+
super(surface, frame);
146+
147+
this._verticalScrollOverflowView = verticalScrollOverflowView;
148+
}
149+
150+
get shouldScroll(): boolean {
151+
return this._shouldScroll;
152+
}
153+
154+
setContentHeight(contentHeight: number) {
155+
if (this._contentHeight !== contentHeight) {
156+
this._contentHeight = contentHeight;
157+
158+
const {height, width} = this.frame.size;
159+
160+
this._scrollThumbRect = {
161+
origin: {
162+
x: this.frame.origin.x,
163+
y: this._scrollThumbRect.origin.y,
164+
},
165+
size: {
166+
width,
167+
height: height * (height / contentHeight),
168+
},
169+
};
170+
171+
this.setNeedsDisplay();
172+
}
173+
}
174+
175+
setShouldScroll(shouldScroll: boolean) {
176+
if (this._shouldScroll !== shouldScroll) {
177+
this._shouldScroll = shouldScroll;
178+
179+
this.setNeedsDisplay();
180+
}
181+
}
182+
183+
setScrollThumbY(value: number) {
184+
const {height} = this.frame.size;
185+
const scrollThumbRect = this._scrollThumbRect;
186+
187+
const maxScrollThumbY = height - scrollThumbRect.size.height;
188+
const newScrollThumbY = Math.max(0, Math.min(maxScrollThumbY, value));
189+
190+
this._scrollThumbRect = {
191+
...scrollThumbRect,
192+
origin: {
193+
x: this.frame.origin.x,
194+
y: newScrollThumbY,
195+
},
196+
};
197+
198+
this.setNeedsDisplay();
199+
200+
const maxContentOffset = this._contentHeight - height;
201+
const contentScrollOffset =
202+
(newScrollThumbY / maxScrollThumbY) * maxContentOffset * -1;
203+
204+
this._verticalScrollOverflowView.setScrollOffset(contentScrollOffset);
205+
}
206+
207+
draw(context: CanvasRenderingContext2D, viewRefs: ViewRefs) {
208+
if (this.shouldScroll) {
209+
const {x, y} = this.frame.origin;
210+
const {width, height} = this.frame.size;
211+
212+
// TODO Use real color
213+
context.fillStyle = COLORS.REACT_RESIZE_BAR;
214+
context.fillRect(x, y, width, height);
215+
216+
// TODO Use real color
217+
context.fillStyle = COLORS.SCROLL_CARET;
218+
context.fillRect(
219+
this._scrollThumbRect.origin.x,
220+
this._scrollThumbRect.origin.y,
221+
this._scrollThumbRect.size.width,
222+
this._scrollThumbRect.size.height,
223+
);
224+
225+
// TODO Use real color
226+
context.fillStyle = COLORS.REACT_RESIZE_BAR_BORDER;
227+
context.fillRect(x, y, BORDER_SIZE, height);
228+
}
229+
}
230+
231+
handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
232+
if (!this.shouldScroll) {
233+
// If content isn't scrollable, ignore.
234+
return;
235+
}
236+
237+
switch (interaction.type) {
238+
case 'click':
239+
this._handleClick(interaction, viewRefs);
240+
break;
241+
case 'mousedown':
242+
this._handleMouseDown(interaction, viewRefs);
243+
break;
244+
case 'mousemove':
245+
this._handleMouseMove(interaction, viewRefs);
246+
break;
247+
case 'mouseup':
248+
this._handleMouseUp(interaction, viewRefs);
249+
break;
250+
}
251+
}
252+
253+
_handleClick(interaction: ClickInteraction, viewRefs: ViewRefs) {
254+
const {location} = interaction.payload;
255+
if (rectContainsPoint(location, this.frame)) {
256+
const currentScrollThumbY = this._scrollThumbRect.origin.y;
257+
const y = location.y;
258+
259+
if (rectContainsPoint(location, this._scrollThumbRect)) {
260+
// Ignore clicks on the track thumb directly.
261+
return;
262+
}
263+
264+
// Scroll up or down about one viewport worth of content:
265+
// TODO This calculation is broken
266+
const deltaY = this.frame.size.height * 0.8;
267+
268+
this.setScrollThumbY(
269+
y < currentScrollThumbY
270+
? currentScrollThumbY - deltaY
271+
: currentScrollThumbY + deltaY,
272+
);
273+
}
274+
}
275+
276+
_handleMouseDown(interaction: MouseDownInteraction, viewRefs: ViewRefs) {
277+
const {location} = interaction.payload;
278+
if (!rectContainsPoint(location, this._scrollThumbRect)) {
279+
return;
280+
}
281+
viewRefs.activeView = this;
282+
283+
this.currentCursor = 'default';
284+
285+
this._isScrolling = true;
286+
this.setNeedsDisplay();
287+
}
288+
289+
_handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
290+
const {event, location} = interaction.payload;
291+
if (rectContainsPoint(location, this.frame)) {
292+
if (viewRefs.hoveredView !== this) {
293+
viewRefs.hoveredView = this;
294+
}
295+
296+
this.currentCursor = 'default';
297+
}
298+
299+
if (viewRefs.activeView === this) {
300+
this.currentCursor = 'default';
301+
302+
this.setScrollThumbY(this._scrollThumbRect.origin.y + event.movementY);
303+
}
304+
}
305+
306+
_handleMouseUp(interaction: MouseUpInteraction, viewRefs: ViewRefs) {
307+
if (viewRefs.activeView === this) {
308+
viewRefs.activeView = null;
309+
}
310+
311+
if (this._isScrolling) {
312+
this._isScrolling = false;
313+
this.setNeedsDisplay();
314+
}
315+
}
316+
}

packages/react-devtools-scheduling-profiler/src/view-base/VerticalScrollView.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ export class VerticalScrollView extends View {
135135

136136
_handleMouseDown(interaction: MouseDownInteraction) {
137137
if (rectContainsPoint(interaction.payload.location, this.frame)) {
138+
console.log('VerticalScrollView()\n location:', interaction.payload.location, '\n frame:', JSON.stringify(this.frame).replace('"', ''), '\n visibleArea:', JSON.stringify(this.visibleArea).replace('"', ''));
138139
this._isPanning = true;
139140
}
140141
}

packages/react-devtools-scheduling-profiler/src/view-base/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export * from './HorizontalPanAndZoomView';
1212
export * from './ResizableView';
1313
export * from './Surface';
1414
export * from './VerticalScrollView';
15+
export * from './VerticalScrollOverflowView';
1516
export * from './View';
1617
export * from './geometry';
1718
export * from './layouter';

packages/react-devtools-shell/src/app/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import Iframe from './Iframe';
1212
import EditableProps from './EditableProps';
1313
import ElementTypes from './ElementTypes';
1414
import Hydration from './Hydration';
15-
import InlineWarnings from './InlineWarnings';
15+
// import InlineWarnings from './InlineWarnings';
1616
import InspectableElements from './InspectableElements';
1717
import ReactNativeWeb from './ReactNativeWeb';
1818
import ToDoList from './ToDoList';
@@ -52,7 +52,7 @@ function mountTestApp() {
5252
mountHelper(Hydration);
5353
mountHelper(ElementTypes);
5454
mountHelper(EditableProps);
55-
mountHelper(InlineWarnings);
55+
// mountHelper(InlineWarnings);
5656
mountHelper(ReactNativeWeb);
5757
mountHelper(Toggle);
5858
mountHelper(ErrorBoundaries);

0 commit comments

Comments
 (0)