chore: use glass pane for dragging (#26513)

This commit is contained in:
Pavel Feldman 2023-08-17 09:36:59 -07:00 committed by GitHub
parent 5bcd1fb65f
commit 049f839b30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 109 additions and 36 deletions

View File

@ -25,10 +25,6 @@
margin-left: 10px; margin-left: 10px;
} }
.timeline-view.dragging {
cursor: ew-resize;
}
.timeline-divider { .timeline-divider {
position: absolute; position: absolute;
width: 1px; width: 1px;
@ -137,6 +133,10 @@
background-color: #3879d91a; background-color: #3879d91a;
} }
body.dark-mode .timeline-window-curtain {
background-color: #161718bf;
}
.timeline-window-curtain.left { .timeline-window-curtain.left {
border-right: 1px solid var(--vscode-panel-border); border-right: 1px solid var(--vscode-panel-border);
} }

View File

@ -1,21 +1,21 @@
/* /**
* Copyright 2017 Google Inc. All rights reserved. * Copyright (c) Microsoft Corporation.
* Modifications copyright (c) Microsoft Corporation. *
* Licensed under the Apache License, Version 2.0 (the "License");
Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License.
you may not use this file except in compliance with the License. * You may obtain a copy of the License at
You may obtain a copy of the License at *
* http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0 *
* Unless required by applicable law or agreed to in writing, software
Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS,
distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and
See the License for the specific language governing permissions and * limitations under the License.
limitations under the License. */
*/
import { msToString, useMeasure } from '@web/uiUtils'; import { msToString, useMeasure } from '@web/uiUtils';
import { GlassPane } from '@web/components/glassPane';
import * as React from 'react'; import * as React from 'react';
import type { Boundaries } from '../geometry'; import type { Boundaries } from '../geometry';
import { FilmStrip } from './filmStrip'; import { FilmStrip } from './filmStrip';
@ -111,16 +111,13 @@ export const Timeline: React.FunctionComponent<{
} }
}, [boundaries, measure, ref, selectedTime]); }, [boundaries, measure, ref, selectedTime]);
const onMouseMove = React.useCallback((event: React.MouseEvent) => { const onGlassPaneMouseMove = React.useCallback((event: MouseEvent) => {
if (!ref.current) if (!ref.current)
return; return;
const x = event.clientX - ref.current.getBoundingClientRect().left; const x = event.clientX - ref.current.getBoundingClientRect().left;
const time = positionToTime(measure.width, boundaries, x); const time = positionToTime(measure.width, boundaries, x);
const action = model?.actions.findLast(action => action.startTime <= time); const action = model?.actions.findLast(action => action.startTime <= time);
if (!dragWindow) {
setPreviewPoint({ x, clientY: event.clientY, action, sdkLanguage });
return;
}
if (!event.buttons) { if (!event.buttons) {
setDragWindow(undefined); setDragWindow(undefined);
return; return;
@ -130,6 +127,10 @@ export const Timeline: React.FunctionComponent<{
if (action) if (action)
onSelected(action); onSelected(action);
// Should not happen, but for type safety.
if (!dragWindow)
return;
let newDragWindow = dragWindow; let newDragWindow = dragWindow;
if (dragWindow.type === 'resize') { if (dragWindow.type === 'resize') {
newDragWindow = { ...dragWindow, endX: x }; newDragWindow = { ...dragWindow, endX: x };
@ -153,9 +154,9 @@ export const Timeline: React.FunctionComponent<{
const time2 = positionToTime(measure.width, boundaries, newDragWindow.endX); const time2 = positionToTime(measure.width, boundaries, newDragWindow.endX);
if (time1 !== time2) if (time1 !== time2)
setSelectedTime({ minimum: Math.min(time1, time2), maximum: Math.max(time1, time2) }); setSelectedTime({ minimum: Math.min(time1, time2), maximum: Math.max(time1, time2) });
}, [boundaries, dragWindow, measure, model, onSelected, ref, sdkLanguage, setSelectedTime]); }, [boundaries, dragWindow, measure, model, onSelected, ref, setSelectedTime]);
const onMouseUp = React.useCallback(() => { const onGlassPaneMouseUp = React.useCallback(() => {
setPreviewPoint(undefined); setPreviewPoint(undefined);
if (!dragWindow) if (!dragWindow)
return; return;
@ -178,23 +179,35 @@ export const Timeline: React.FunctionComponent<{
setDragWindow(undefined); setDragWindow(undefined);
}, [boundaries, dragWindow, measure, model, selectedTime, setSelectedTime, onSelected]); }, [boundaries, dragWindow, measure, model, selectedTime, setSelectedTime, onSelected]);
const onMouseMove = React.useCallback((event: React.MouseEvent) => {
if (!ref.current)
return;
const x = event.clientX - ref.current.getBoundingClientRect().left;
const time = positionToTime(measure.width, boundaries, x);
const action = model?.actions.findLast(action => action.startTime <= time);
setPreviewPoint({ x, clientY: event.clientY, action, sdkLanguage });
}, [boundaries, measure, model, ref, sdkLanguage]);
const onMouseLeave = React.useCallback(() => { const onMouseLeave = React.useCallback(() => {
setPreviewPoint(undefined); setPreviewPoint(undefined);
}, []); }, []);
const onDoubleClick = React.useCallback(() => { const onPaneDoubleClick = React.useCallback(() => {
setSelectedTime(undefined); setSelectedTime(undefined);
}, [setSelectedTime]); }, [setSelectedTime]);
return <div style={{ flex: 'none', borderBottom: '1px solid var(--vscode-panel-border)' }}> return <div style={{ flex: 'none', borderBottom: '1px solid var(--vscode-panel-border)' }}>
<div <GlassPane
ref={ref} enabled={!!dragWindow}
className={'timeline-view' + (dragWindow ? ' dragging' : '')} cursor={dragWindow?.type === 'resize' ? 'ew-resize' : 'grab'}
onPaneMouseUp={onGlassPaneMouseUp}
onPaneMouseMove={onGlassPaneMouseMove}
onPaneDoubleClick={onPaneDoubleClick} />
<div ref={ref}
className='timeline-view'
onMouseDown={onMouseDown} onMouseDown={onMouseDown}
onMouseUp={onMouseUp}
onMouseMove={onMouseMove} onMouseMove={onMouseMove}
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}>
onDoubleClick={onDoubleClick}>
<div className='timeline-grid'>{ <div className='timeline-grid'>{
offsets.map((offset, index) => { offsets.map((offset, index) => {
return <div key={index} className='timeline-divider' style={{ left: offset.position + 'px' }}> return <div key={index} className='timeline-divider' style={{ left: offset.position + 'px' }}>
@ -219,7 +232,7 @@ export const Timeline: React.FunctionComponent<{
display: (previewPoint !== undefined) ? 'block' : 'none', display: (previewPoint !== undefined) ? 'block' : 'none',
left: (previewPoint?.x || 0) + 'px', left: (previewPoint?.x || 0) + 'px',
}} /> }} />
<div className='timeline-window'> {selectedTime && <div className='timeline-window'>
<div className='timeline-window-curtain left' style={{ width: curtainLeft }}></div> <div className='timeline-window-curtain left' style={{ width: curtainLeft }}></div>
<div className='timeline-window-resizer'></div> <div className='timeline-window-resizer'></div>
<div className='timeline-window-center'> <div className='timeline-window-center'>
@ -227,7 +240,7 @@ export const Timeline: React.FunctionComponent<{
</div> </div>
<div className='timeline-window-resizer'></div> <div className='timeline-window-resizer'></div>
<div className='timeline-window-curtain right' style={{ width: curtainRight }}></div> <div className='timeline-window-curtain right' style={{ width: curtainRight }}></div>
</div> </div>}
</div> </div>
</div>; </div>;
}; };

View File

@ -0,0 +1,60 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
export const GlassPane: React.FC<{
enabled: boolean;
cursor: string;
onPaneMouseMove?: (e: MouseEvent) => void;
onPaneMouseUp?: (e: MouseEvent) => void;
onPaneDoubleClick?: (e: MouseEvent) => void;
}> = ({ enabled, cursor, onPaneMouseMove, onPaneMouseUp, onPaneDoubleClick }) => {
React.useEffect(() => {
if (!enabled)
return;
const glassPaneDiv = document.createElement('div');
glassPaneDiv.style.position = 'absolute';
glassPaneDiv.style.top = '0';
glassPaneDiv.style.right = '0';
glassPaneDiv.style.bottom = '0';
glassPaneDiv.style.left = '0';
glassPaneDiv.style.zIndex = '9999';
glassPaneDiv.style.cursor = cursor;
document.body.appendChild(glassPaneDiv);
if (onPaneMouseMove)
glassPaneDiv.addEventListener('mousemove', onPaneMouseMove);
if (onPaneMouseUp)
glassPaneDiv.addEventListener('mouseup', onPaneMouseUp);
if (onPaneDoubleClick)
document.body.addEventListener('dblclick', onPaneDoubleClick);
return () => {
if (onPaneMouseMove)
glassPaneDiv.removeEventListener('mousemove', onPaneMouseMove);
if (onPaneMouseUp)
glassPaneDiv.removeEventListener('mouseup', onPaneMouseUp);
if (onPaneDoubleClick)
document.body.removeEventListener('dblclick', onPaneDoubleClick);
document.body.removeChild(glassPaneDiv);
};
}, [enabled, cursor, onPaneMouseMove, onPaneMouseUp, onPaneDoubleClick]);
return <></>;
};