mirror of
https://github.com/microsoft/vscode.git
synced 2026-02-15 07:28:05 +00:00
Merge pull request #295231 from microsoft/eli/revert-motion
Reverts resize panel motion
This commit is contained in:
@@ -9,10 +9,9 @@ import { Event } from '../../../common/event.js';
|
||||
import { Disposable } from '../../../common/lifecycle.js';
|
||||
import './gridview.css';
|
||||
import { Box, GridView, IGridViewOptions, IGridViewStyles, IView as IGridViewView, IViewSize, orthogonal, Sizing as GridViewSizing, GridLocation } from './gridview.js';
|
||||
import type { SplitView, AutoSizing as SplitViewAutoSizing, IViewVisibilityAnimationOptions } from '../splitview/splitview.js';
|
||||
import type { SplitView, AutoSizing as SplitViewAutoSizing } from '../splitview/splitview.js';
|
||||
|
||||
export type { IViewSize };
|
||||
export type { IViewVisibilityAnimationOptions } from '../splitview/splitview.js';
|
||||
export { LayoutPriority, Orientation, orthogonal } from './gridview.js';
|
||||
|
||||
export const enum Direction {
|
||||
@@ -651,12 +650,10 @@ export class Grid<T extends IView = IView> extends Disposable {
|
||||
* Set the visibility state of a {@link IView view}.
|
||||
*
|
||||
* @param view The {@link IView view}.
|
||||
* @param visible Whether the view should be visible.
|
||||
* @param animation Optional animation options.
|
||||
*/
|
||||
setViewVisible(view: T, visible: boolean, animation?: IViewVisibilityAnimationOptions): void {
|
||||
setViewVisible(view: T, visible: boolean): void {
|
||||
const location = this.getViewLocation(view);
|
||||
this.gridview.setViewVisible(location, visible, animation);
|
||||
this.gridview.setViewVisible(location, visible);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { $ } from '../../dom.js';
|
||||
import { IBoundarySashes, Orientation, Sash } from '../sash/sash.js';
|
||||
import { DistributeSizing, ISplitViewStyles, IView as ISplitView, IViewVisibilityAnimationOptions, LayoutPriority, Sizing, AutoSizing, SplitView } from '../splitview/splitview.js';
|
||||
import { DistributeSizing, ISplitViewStyles, IView as ISplitView, LayoutPriority, Sizing, AutoSizing, SplitView } from '../splitview/splitview.js';
|
||||
import { equals as arrayEquals, tail } from '../../../common/arrays.js';
|
||||
import { Color } from '../../../common/color.js';
|
||||
import { Emitter, Event, Relay } from '../../../common/event.js';
|
||||
@@ -615,7 +615,7 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
return this.splitview.isViewVisible(index);
|
||||
}
|
||||
|
||||
setChildVisible(index: number, visible: boolean, animation?: IViewVisibilityAnimationOptions): void {
|
||||
setChildVisible(index: number, visible: boolean): void {
|
||||
index = validateIndex(index, this.children.length);
|
||||
|
||||
if (this.splitview.isViewVisible(index) === visible) {
|
||||
@@ -623,7 +623,7 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
}
|
||||
|
||||
const wereAllChildrenHidden = this.splitview.contentSize === 0;
|
||||
this.splitview.setViewVisible(index, visible, animation);
|
||||
this.splitview.setViewVisible(index, visible);
|
||||
const areAllChildrenHidden = this.splitview.contentSize === 0;
|
||||
|
||||
// If all children are hidden then the parent should hide the entire splitview
|
||||
@@ -1663,7 +1663,7 @@ export class GridView implements IDisposable {
|
||||
*
|
||||
* @param location The {@link GridLocation location} of the view.
|
||||
*/
|
||||
setViewVisible(location: GridLocation, visible: boolean, animation?: IViewVisibilityAnimationOptions): void {
|
||||
setViewVisible(location: GridLocation, visible: boolean): void {
|
||||
if (this.hasMaximizedView()) {
|
||||
this.exitMaximizedView();
|
||||
return;
|
||||
@@ -1676,7 +1676,7 @@ export class GridView implements IDisposable {
|
||||
throw new Error('Invalid from location');
|
||||
}
|
||||
|
||||
parent.setChildVisible(index, visible, animation);
|
||||
parent.setChildVisible(index, visible);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* Utility class applied during panel animations to prevent content overflow */
|
||||
.monaco-split-view2 > .split-view-container > .split-view-view.motion-animating {
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import './motion.css';
|
||||
|
||||
//#region Easing Curves
|
||||
|
||||
/**
|
||||
* A pre-parsed cubic bezier easing curve that can be evaluated directly
|
||||
* without reparsing a CSS string on every frame.
|
||||
*
|
||||
* Given control points `(x1, y1)` and `(x2, y2)` (the CSS `cubic-bezier`
|
||||
* parameters), {@link solve} finds the bezier parameter `u` such that
|
||||
* `Bx(u) = t` using Newton's method, then returns `By(u)`.
|
||||
*/
|
||||
export class CubicBezierCurve {
|
||||
|
||||
constructor(
|
||||
readonly x1: number,
|
||||
readonly y1: number,
|
||||
readonly x2: number,
|
||||
readonly y2: number,
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Evaluate the curve at time `t` (0-1), returning the eased value.
|
||||
*/
|
||||
solve(t: number): number {
|
||||
if (t <= 0) {
|
||||
return 0;
|
||||
}
|
||||
if (t >= 1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Newton's method to find u where Bx(u) = t
|
||||
let u = t; // initial guess
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const currentX = bezierComponent(u, this.x1, this.x2);
|
||||
const error = currentX - t;
|
||||
if (Math.abs(error) < 1e-6) {
|
||||
break;
|
||||
}
|
||||
const dx = bezierComponentDerivative(u, this.x1, this.x2);
|
||||
if (Math.abs(dx) < 1e-6) {
|
||||
break;
|
||||
}
|
||||
u -= error / dx;
|
||||
}
|
||||
|
||||
u = Math.max(0, Math.min(1, u));
|
||||
return bezierComponent(u, this.y1, this.y2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the CSS `cubic-bezier(…)` string representation, for use in
|
||||
* CSS `transition` or `animation` properties.
|
||||
*/
|
||||
toCssString(): string {
|
||||
return `cubic-bezier(${this.x1}, ${this.y1}, ${this.x2}, ${this.y2})`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent 2 ease-out curve - default for entrances and expansions.
|
||||
* Starts fast and decelerates to a stop.
|
||||
*/
|
||||
export const EASE_OUT = new CubicBezierCurve(0.1, 0.9, 0.2, 1);
|
||||
|
||||
/**
|
||||
* Fluent 2 ease-in curve - for exits and collapses.
|
||||
* Starts slow and accelerates out.
|
||||
*/
|
||||
export const EASE_IN = new CubicBezierCurve(0.9, 0.1, 1, 0.2);
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Cubic Bezier Evaluation
|
||||
|
||||
/**
|
||||
* Parses a CSS `cubic-bezier(x1, y1, x2, y2)` string into a
|
||||
* {@link CubicBezierCurve}. Returns a linear curve on parse failure.
|
||||
*/
|
||||
export function parseCubicBezier(css: string): CubicBezierCurve {
|
||||
const match = css.match(/cubic-bezier\(\s*([-\d.]+)\s*,\s*([-\d.]+)\s*,\s*([-\d.]+)\s*,\s*([-\d.]+)\s*\)/);
|
||||
if (!match) {
|
||||
return new CubicBezierCurve(0, 0, 1, 1);
|
||||
}
|
||||
return new CubicBezierCurve(parseFloat(match[1]), parseFloat(match[2]), parseFloat(match[3]), parseFloat(match[4]));
|
||||
}
|
||||
|
||||
/** Evaluates one component of a cubic bezier: B(u) with control points p1, p2, endpoints 0 and 1. */
|
||||
function bezierComponent(u: number, p1: number, p2: number): number {
|
||||
// B(u) = 3(1-u)^2*u*p1 + 3(1-u)*u^2*p2 + u^3
|
||||
const oneMinusU = 1 - u;
|
||||
return 3 * oneMinusU * oneMinusU * u * p1 + 3 * oneMinusU * u * u * p2 + u * u * u;
|
||||
}
|
||||
|
||||
/** First derivative of a bezier component: B'(u). */
|
||||
function bezierComponentDerivative(u: number, p1: number, p2: number): number {
|
||||
// B'(u) = 3(1-u)^2*p1 + 6(1-u)*u*(p2-p1) + 3*u^2*(1-p2)
|
||||
const oneMinusU = 1 - u;
|
||||
return 3 * oneMinusU * oneMinusU * p1 + 6 * oneMinusU * u * (p2 - p1) + 3 * u * u * (1 - p2);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Duration Scaling
|
||||
|
||||
/**
|
||||
* Reference pixel distance at which the base duration constants apply.
|
||||
* Duration scales linearly: a 600px animation takes twice as long as a 300px
|
||||
* one, keeping perceived velocity constant.
|
||||
*/
|
||||
const REFERENCE_DISTANCE = 300;
|
||||
|
||||
/** Minimum animation duration in milliseconds (avoids sub-frame flickers). */
|
||||
const MIN_DURATION = 50;
|
||||
|
||||
/** Maximum animation duration in milliseconds (avoids sluggish feel). */
|
||||
const MAX_DURATION = 300;
|
||||
|
||||
/**
|
||||
* Scales a base animation duration proportionally to the pixel distance
|
||||
* being animated, so that perceived velocity stays constant regardless of
|
||||
* panel width.
|
||||
*
|
||||
* @param baseDuration The duration (ms) that applies at {@link REFERENCE_DISTANCE} pixels.
|
||||
* @param pixelDistance The actual number of pixels the view will resize.
|
||||
* @returns The scaled duration, clamped to [{@link MIN_DURATION}, {@link MAX_DURATION}].
|
||||
*/
|
||||
export function scaleDuration(baseDuration: number, pixelDistance: number): number {
|
||||
if (pixelDistance <= 0) {
|
||||
return baseDuration;
|
||||
}
|
||||
const scaled = baseDuration * (pixelDistance / REFERENCE_DISTANCE);
|
||||
return Math.round(Math.max(MIN_DURATION, Math.min(MAX_DURATION, scaled)));
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Utility Functions
|
||||
|
||||
/**
|
||||
* Checks whether motion is reduced by looking for the `monaco-reduce-motion`
|
||||
* class on an ancestor element. This integrates with VS Code's existing
|
||||
* accessibility infrastructure in {@link AccessibilityService}.
|
||||
*/
|
||||
export function isMotionReduced(element: HTMLElement): boolean {
|
||||
return element.closest('.monaco-reduce-motion') !== null;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@@ -8,41 +8,15 @@ import { DomEmitter } from '../../event.js';
|
||||
import { ISashEvent as IBaseSashEvent, Orientation, Sash, SashState } from '../sash/sash.js';
|
||||
import { SmoothScrollableElement } from '../scrollbar/scrollableElement.js';
|
||||
import { pushToEnd, pushToStart, range } from '../../../common/arrays.js';
|
||||
import { CancellationToken } from '../../../common/cancellation.js';
|
||||
import { Color } from '../../../common/color.js';
|
||||
import { Emitter, Event } from '../../../common/event.js';
|
||||
import { combinedDisposable, Disposable, dispose, IDisposable, toDisposable } from '../../../common/lifecycle.js';
|
||||
import { clamp } from '../../../common/numbers.js';
|
||||
import { Scrollable, ScrollbarVisibility, ScrollEvent } from '../../../common/scrollable.js';
|
||||
import * as types from '../../../common/types.js';
|
||||
import { CubicBezierCurve, isMotionReduced, scaleDuration } from '../motion/motion.js';
|
||||
import './splitview.css';
|
||||
export { Orientation } from '../sash/sash.js';
|
||||
|
||||
/**
|
||||
* Options for animating a view visibility change in a {@link SplitView}.
|
||||
*/
|
||||
export interface IViewVisibilityAnimationOptions {
|
||||
|
||||
/** Transition duration in milliseconds. */
|
||||
readonly duration: number;
|
||||
|
||||
/** The easing curve applied to the animation. */
|
||||
readonly easing: CubicBezierCurve;
|
||||
|
||||
/**
|
||||
* Optional callback invoked when the animation finishes naturally.
|
||||
* NOT called if the animation is cancelled via the {@link token}.
|
||||
*/
|
||||
readonly onComplete?: () => void;
|
||||
|
||||
/**
|
||||
* A cancellation token that allows the caller to stop the animation.
|
||||
* When cancellation is requested the animation snaps to its final state.
|
||||
*/
|
||||
readonly token: CancellationToken;
|
||||
}
|
||||
|
||||
export interface ISplitViewStyles {
|
||||
readonly separatorBorder: Color;
|
||||
}
|
||||
@@ -828,38 +802,14 @@ export class SplitView<TLayoutContext = undefined, TView extends IView<TLayoutCo
|
||||
/**
|
||||
* Set a {@link IView view}'s visibility.
|
||||
*
|
||||
* When {@link animation} is provided and motion is not reduced, the
|
||||
* visibility change is animated. Otherwise the change is applied
|
||||
* instantly. Any in-flight animation is always cancelled first.
|
||||
*
|
||||
* @param index The {@link IView view} index.
|
||||
* @param visible Whether the {@link IView view} should be visible.
|
||||
* @param animation Optional animation options. When omitted (or when
|
||||
* the user prefers reduced motion) the change is instant.
|
||||
*/
|
||||
setViewVisible(index: number, visible: boolean, animation?: IViewVisibilityAnimationOptions): void {
|
||||
setViewVisible(index: number, visible: boolean): void {
|
||||
if (index < 0 || index >= this.viewItems.length) {
|
||||
throw new Error('Index out of bounds');
|
||||
}
|
||||
|
||||
// Cancel any in-flight animation before changing visibility.
|
||||
// An animated visibility change interpolates ALL view sizes each
|
||||
// frame, so a concurrent change on a different view would be
|
||||
// overwritten on the next frame. Snapping first prevents that.
|
||||
this._cleanupMotion?.();
|
||||
this._cleanupMotion = undefined;
|
||||
|
||||
if (animation && !animation.token.isCancellationRequested && !isMotionReduced(this.el) && this.viewItems[index].visible !== visible) {
|
||||
this._setViewVisibleAnimated(index, visible, animation);
|
||||
} else {
|
||||
this._setViewVisibleInstant(index, visible);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the visibility change to the model without animation.
|
||||
*/
|
||||
private _setViewVisibleInstant(index: number, visible: boolean): void {
|
||||
const viewItem = this.viewItems[index];
|
||||
viewItem.setVisible(visible);
|
||||
|
||||
@@ -868,167 +818,6 @@ export class SplitView<TLayoutContext = undefined, TView extends IView<TLayoutCo
|
||||
this.saveProportions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate the visibility change using `requestAnimationFrame`.
|
||||
*
|
||||
* Interpolates all view sizes on each frame, which naturally cascades
|
||||
* layout changes through nested splitviews in the grid hierarchy
|
||||
* (e.g., the bottom panel resizing when the sidebar animates).
|
||||
*
|
||||
* The animation can be cancelled via {@link IViewVisibilityAnimationOptions.token}.
|
||||
* {@link IViewVisibilityAnimationOptions.onComplete} is only called when the
|
||||
* animation finishes naturally (not on cancellation).
|
||||
*/
|
||||
private _setViewVisibleAnimated(index: number, visible: boolean, animation: IViewVisibilityAnimationOptions): void {
|
||||
const { duration: baseDuration, easing, onComplete, token } = animation;
|
||||
|
||||
const container = this.viewContainer.children[index] as HTMLElement;
|
||||
const window = getWindow(this.el);
|
||||
let disposed = false;
|
||||
let rafId: number | undefined;
|
||||
|
||||
// 1. Snapshot sizes BEFORE the visibility change (the animation start state)
|
||||
const startSizes = this.viewItems.map(v => v.size);
|
||||
|
||||
// 2. Apply the target visibility to the model instantly.
|
||||
// This computes final sizes, fires events, updates sashes, etc.
|
||||
this._setViewVisibleInstant(index, visible);
|
||||
|
||||
// 3. Snapshot sizes AFTER the visibility change (the animation end state)
|
||||
const finalSizes = this.viewItems.map(v => v.size);
|
||||
|
||||
// 4. Restore start sizes so we can animate FROM them
|
||||
for (let i = 0; i < this.viewItems.length; i++) {
|
||||
this.viewItems[i].size = startSizes[i];
|
||||
}
|
||||
|
||||
// 5. For hiding: the target container lost .visible class (→ display:none).
|
||||
// Restore it so content stays visible during the animation.
|
||||
if (!visible) {
|
||||
container.classList.add('visible');
|
||||
}
|
||||
|
||||
// 6. Clip overflow on the animating container so that the view
|
||||
// content (rendered at its full target size) is revealed/hidden
|
||||
// smoothly as the container width animates.
|
||||
container.style.overflow = 'hidden';
|
||||
|
||||
// The target size is the full (non-zero) panel size. During
|
||||
// animation we re-layout the animating view at this fixed size
|
||||
// after each frame so its content never reflows at intermediate
|
||||
// widths/heights (prevents chat, extension icons, etc. from
|
||||
// jumping around). Sibling views still receive interpolated sizes
|
||||
// so the editor / bottom panel remain responsive.
|
||||
const viewTargetSize = visible ? finalSizes[index] : startSizes[index];
|
||||
|
||||
// 6b. Set initial opacity for fade effect
|
||||
container.style.opacity = visible ? '0' : '1';
|
||||
|
||||
// 7. Scale duration based on pixel distance for consistent perceived velocity
|
||||
const pixelDistance = Math.abs(finalSizes[index] - startSizes[index]);
|
||||
const duration = scaleDuration(baseDuration, pixelDistance);
|
||||
|
||||
// 8. Render the start state
|
||||
this.layoutViews();
|
||||
try {
|
||||
this.viewItems[index].view.layout(viewTargetSize, 0, this.layoutContext);
|
||||
} catch (e) {
|
||||
console.error('Splitview: Failed to layout view during animation');
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
// 9. Easing curve is pre-parsed - ready for JS evaluation
|
||||
|
||||
// Helper: snap all sizes to final state and clean up
|
||||
const applyFinalState = () => {
|
||||
for (let i = 0; i < this.viewItems.length; i++) {
|
||||
this.viewItems[i].size = finalSizes[i];
|
||||
}
|
||||
container.style.opacity = '';
|
||||
container.style.overflow = '';
|
||||
if (!visible) {
|
||||
container.classList.remove('visible');
|
||||
}
|
||||
this.layoutViews();
|
||||
this.saveProportions();
|
||||
};
|
||||
|
||||
const cleanup = (completed: boolean) => {
|
||||
if (disposed) {
|
||||
return;
|
||||
}
|
||||
disposed = true;
|
||||
tokenListener.dispose();
|
||||
if (rafId !== undefined) {
|
||||
window.cancelAnimationFrame(rafId);
|
||||
rafId = undefined;
|
||||
}
|
||||
applyFinalState();
|
||||
this._cleanupMotion = undefined;
|
||||
if (completed) {
|
||||
onComplete?.();
|
||||
}
|
||||
};
|
||||
this._cleanupMotion = () => cleanup(false);
|
||||
|
||||
// Listen to the cancellation token so the caller can stop the animation
|
||||
const tokenListener = token.onCancellationRequested(() => cleanup(false));
|
||||
|
||||
// 10. Animate via requestAnimationFrame
|
||||
const startTime = performance.now();
|
||||
const totalSize = this.size;
|
||||
|
||||
const animate = () => {
|
||||
if (disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const elapsed = performance.now() - startTime;
|
||||
const t = Math.min(elapsed / duration, 1);
|
||||
const easedT = easing.solve(t);
|
||||
|
||||
// Interpolate opacity for fade effect
|
||||
container.style.opacity = String(visible ? easedT : 1 - easedT);
|
||||
|
||||
// Interpolate all view sizes
|
||||
let runningTotal = 0;
|
||||
for (let i = 0; i < this.viewItems.length; i++) {
|
||||
if (i === this.viewItems.length - 1) {
|
||||
// Last item absorbs rounding errors to maintain total = this.size
|
||||
this.viewItems[i].size = totalSize - runningTotal;
|
||||
} else {
|
||||
const size = Math.round(
|
||||
startSizes[i] + (finalSizes[i] - startSizes[i]) * easedT
|
||||
);
|
||||
this.viewItems[i].size = size;
|
||||
runningTotal += size;
|
||||
}
|
||||
}
|
||||
|
||||
this.layoutViews();
|
||||
|
||||
// Re-layout the animating view at its full target size so its
|
||||
// content does not reflow at intermediate sizes. The container
|
||||
// is already at the interpolated size with overflow:hidden.
|
||||
try {
|
||||
this.viewItems[index].view.layout(viewTargetSize, 0, this.layoutContext);
|
||||
} catch (e) {
|
||||
console.error('Splitview: Failed to layout view during animation');
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
if (t < 1) {
|
||||
rafId = window.requestAnimationFrame(animate);
|
||||
} else {
|
||||
cleanup(true);
|
||||
}
|
||||
};
|
||||
|
||||
rafId = window.requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
private _cleanupMotion: (() => void) | undefined;
|
||||
|
||||
/**
|
||||
* Returns the {@link IView view}'s size previously to being hidden.
|
||||
*
|
||||
|
||||
@@ -525,35 +525,3 @@
|
||||
outline: 1px solid var(--vscode-list-focusOutline) !important;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
/* Entrance animation */
|
||||
@keyframes quick-input-entrance {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.quick-input-widget.animating-entrance {
|
||||
animation: quick-input-entrance 150ms cubic-bezier(0.1, 0.9, 0.2, 1) forwards;
|
||||
}
|
||||
|
||||
/* Exit animation */
|
||||
@keyframes quick-input-exit {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
}
|
||||
|
||||
.quick-input-widget.animating-exit {
|
||||
animation: quick-input-exit 50ms cubic-bezier(0.9, 0.1, 1, 0.2) forwards;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ import { TriStateCheckbox, createToggleActionViewItemProvider } from '../../../b
|
||||
import { defaultCheckboxStyles } from '../../theme/browser/defaultStyles.js';
|
||||
import { QuickInputTreeController } from './tree/quickInputTreeController.js';
|
||||
import { QuickTree } from './tree/quickTree.js';
|
||||
import { isMotionReduced } from '../../../base/browser/ui/motion/motion.js';
|
||||
import { AnchorAlignment, AnchorPosition, layout2d } from '../../../base/common/layout.js';
|
||||
import { getAnchorRect } from '../../../base/browser/ui/contextview/contextview.js';
|
||||
|
||||
@@ -81,7 +80,6 @@ export class QuickInputController extends Disposable {
|
||||
|
||||
private viewState: QuickInputViewState | undefined;
|
||||
private dndController: QuickInputDragAndDropController | undefined;
|
||||
private _cancelExitAnimation: (() => void) | undefined;
|
||||
|
||||
private readonly inQuickInputContext: IContextKey<boolean>;
|
||||
private readonly quickInputTypeContext: IContextKey<QuickInputType>;
|
||||
@@ -713,26 +711,12 @@ export class QuickInputController extends Disposable {
|
||||
const backKeybindingLabel = this.options.backKeybindingLabel();
|
||||
backButton.tooltip = backKeybindingLabel ? localize('quickInput.backWithKeybinding', "Back ({0})", backKeybindingLabel) : localize('quickInput.back', "Back");
|
||||
|
||||
const wasVisible = ui.container.style.display !== 'none';
|
||||
ui.container.style.display = '';
|
||||
// Cancel any in-flight exit animation that would set display:none
|
||||
this._cancelExitAnimation?.();
|
||||
this._cancelExitAnimation = undefined;
|
||||
this.updateLayout();
|
||||
this.dndController?.setEnabled(!controller.anchor);
|
||||
this.dndController?.layoutContainer();
|
||||
ui.inputBox.setFocus();
|
||||
this.quickInputTypeContext.set(controller.type);
|
||||
|
||||
// Animate entrance: fade in + slide down (only when first appearing)
|
||||
if (!wasVisible && !isMotionReduced(ui.container)) {
|
||||
ui.container.classList.add('animating-entrance');
|
||||
const onAnimationEnd = () => {
|
||||
ui.container.classList.remove('animating-entrance');
|
||||
ui.container.removeEventListener('animationend', onAnimationEnd);
|
||||
};
|
||||
ui.container.addEventListener('animationend', onAnimationEnd);
|
||||
}
|
||||
}
|
||||
|
||||
isVisible(): boolean {
|
||||
@@ -799,24 +783,7 @@ export class QuickInputController extends Disposable {
|
||||
this.controller = null;
|
||||
this.onHideEmitter.fire();
|
||||
if (container) {
|
||||
// Animate exit: fade out + slide up (faster than open)
|
||||
if (!isMotionReduced(container)) {
|
||||
container.classList.add('animating-exit');
|
||||
const cleanupAnimation = () => {
|
||||
container.classList.remove('animating-exit');
|
||||
container.removeEventListener('animationend', onAnimationEnd);
|
||||
this._cancelExitAnimation = undefined;
|
||||
};
|
||||
const onAnimationEnd = () => {
|
||||
// Set display after animation completes to actually hide the element
|
||||
container.style.display = 'none';
|
||||
cleanupAnimation();
|
||||
};
|
||||
this._cancelExitAnimation = cleanupAnimation;
|
||||
container.addEventListener('animationend', onAnimationEnd);
|
||||
} else {
|
||||
container.style.display = 'none';
|
||||
}
|
||||
container.style.display = 'none';
|
||||
}
|
||||
if (!focusChanged) {
|
||||
let currentElement = this.previousFocusElement;
|
||||
|
||||
@@ -23,7 +23,7 @@ import { IHostService } from '../services/host/browser/host.js';
|
||||
import { IBrowserWorkbenchEnvironmentService } from '../services/environment/browser/environmentService.js';
|
||||
import { IEditorService } from '../services/editor/common/editorService.js';
|
||||
import { EditorGroupLayout, GroupActivationReason, GroupOrientation, GroupsOrder, IEditorGroupsService } from '../services/editor/common/editorGroupsService.js';
|
||||
import { SerializableGrid, ISerializableView, ISerializedGrid, Orientation, ISerializedNode, ISerializedLeafNode, Direction, IViewSize, Sizing, IViewVisibilityAnimationOptions } from '../../base/browser/ui/grid/grid.js';
|
||||
import { SerializableGrid, ISerializableView, ISerializedGrid, Orientation, ISerializedNode, ISerializedLeafNode, Direction, IViewSize, Sizing } from '../../base/browser/ui/grid/grid.js';
|
||||
import { Part } from './part.js';
|
||||
import { IStatusbarService } from '../services/statusbar/browser/statusbar.js';
|
||||
import { IFileService } from '../../platform/files/common/files.js';
|
||||
@@ -47,8 +47,6 @@ import { AuxiliaryBarPart } from './parts/auxiliarybar/auxiliaryBarPart.js';
|
||||
import { ITelemetryService } from '../../platform/telemetry/common/telemetry.js';
|
||||
import { IAuxiliaryWindowService } from '../services/auxiliaryWindow/browser/auxiliaryWindowService.js';
|
||||
import { CodeWindow, mainWindow } from '../../base/browser/window.js';
|
||||
import { EASE_OUT, EASE_IN } from '../../base/browser/ui/motion/motion.js';
|
||||
import { CancellationToken } from '../../base/common/cancellation.js';
|
||||
|
||||
//#region Layout Implementation
|
||||
|
||||
@@ -1870,32 +1868,27 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
|
||||
this.stateModel.setRuntimeValue(LayoutStateKeys.SIDEBAR_HIDDEN, hidden);
|
||||
|
||||
// Adjust CSS - for hiding, defer adding the class until animation
|
||||
// completes so the part stays visible during the exit animation.
|
||||
if (!hidden) {
|
||||
// Adjust CSS
|
||||
if (hidden) {
|
||||
this.mainContainer.classList.add(LayoutClasses.SIDEBAR_HIDDEN);
|
||||
} else {
|
||||
this.mainContainer.classList.remove(LayoutClasses.SIDEBAR_HIDDEN);
|
||||
}
|
||||
|
||||
// Propagate to grid
|
||||
this.workbenchGrid.setViewVisible(
|
||||
this.sideBarPartView,
|
||||
!hidden,
|
||||
createViewVisibilityAnimation(hidden, () => {
|
||||
if (!hidden) { return; }
|
||||
// Deferred to after close animation
|
||||
this.mainContainer.classList.add(LayoutClasses.SIDEBAR_HIDDEN);
|
||||
if (this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar)) {
|
||||
this.paneCompositeService.hideActivePaneComposite(ViewContainerLocation.Sidebar);
|
||||
this.workbenchGrid.setViewVisible(this.sideBarPartView, !hidden);
|
||||
|
||||
if (!this.isAuxiliaryBarMaximized()) {
|
||||
this.focusPanelOrEditor(); // do not auto focus when auxiliary bar is maximized
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
// If sidebar becomes hidden, also hide the current active Viewlet if any
|
||||
if (hidden && this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar)) {
|
||||
this.paneCompositeService.hideActivePaneComposite(ViewContainerLocation.Sidebar);
|
||||
|
||||
if (!this.isAuxiliaryBarMaximized()) {
|
||||
this.focusPanelOrEditor(); // do not auto focus when auxiliary bar is maximized
|
||||
}
|
||||
}
|
||||
|
||||
// If sidebar becomes visible, show last active Viewlet or default viewlet
|
||||
if (!hidden && !this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar)) {
|
||||
else if (!hidden && !this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar)) {
|
||||
const viewletToOpen = this.paneCompositeService.getLastActivePaneCompositeId(ViewContainerLocation.Sidebar);
|
||||
if (viewletToOpen) {
|
||||
this.openViewContainer(ViewContainerLocation.Sidebar, viewletToOpen);
|
||||
@@ -2019,6 +2012,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
|
||||
const panelOpensMaximized = this.panelOpensMaximized();
|
||||
|
||||
// Adjust CSS
|
||||
if (hidden) {
|
||||
this.mainContainer.classList.add(LayoutClasses.PANEL_HIDDEN);
|
||||
} else {
|
||||
this.mainContainer.classList.remove(LayoutClasses.PANEL_HIDDEN);
|
||||
}
|
||||
|
||||
// If maximized and in process of hiding, unmaximize FIRST before
|
||||
// changing visibility to prevent conflict with setEditorHidden
|
||||
// which would force panel visible again (fixes #281772)
|
||||
@@ -2026,30 +2026,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
this.toggleMaximizedPanel();
|
||||
}
|
||||
|
||||
// Adjust CSS - for hiding, defer adding the class until animation
|
||||
// completes because `.nopanel .part.panel { display: none !important }`
|
||||
// would instantly hide the panel content mid-animation.
|
||||
if (!hidden) {
|
||||
this.mainContainer.classList.remove(LayoutClasses.PANEL_HIDDEN);
|
||||
}
|
||||
|
||||
// Propagate layout changes to grid
|
||||
this.workbenchGrid.setViewVisible(
|
||||
this.panelPartView,
|
||||
!hidden,
|
||||
createViewVisibilityAnimation(hidden, () => {
|
||||
if (!hidden) { return; }
|
||||
// Deferred to after close animation
|
||||
this.mainContainer.classList.add(LayoutClasses.PANEL_HIDDEN);
|
||||
if (this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Panel)) {
|
||||
this.paneCompositeService.hideActivePaneComposite(ViewContainerLocation.Panel);
|
||||
}
|
||||
})
|
||||
);
|
||||
this.workbenchGrid.setViewVisible(this.panelPartView, !hidden);
|
||||
|
||||
// If panel part becomes hidden, focus the editor after animation starts
|
||||
// If panel part becomes hidden, also hide the current active panel if any
|
||||
let focusEditor = false;
|
||||
if (hidden && this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Panel)) {
|
||||
this.paneCompositeService.hideActivePaneComposite(ViewContainerLocation.Panel);
|
||||
if (
|
||||
!isIOS && // do not auto focus on iOS (https://github.com/microsoft/vscode/issues/127832)
|
||||
!this.isAuxiliaryBarMaximized() // do not auto focus when auxiliary bar is maximized
|
||||
@@ -2223,30 +2206,24 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
|
||||
this.stateModel.setRuntimeValue(LayoutStateKeys.AUXILIARYBAR_HIDDEN, hidden);
|
||||
|
||||
// Adjust CSS - for hiding, defer adding the class until animation
|
||||
// completes because `.noauxiliarybar .part.auxiliarybar { display: none !important }`
|
||||
// would instantly hide the content mid-animation.
|
||||
if (!hidden) {
|
||||
// Adjust CSS
|
||||
if (hidden) {
|
||||
this.mainContainer.classList.add(LayoutClasses.AUXILIARYBAR_HIDDEN);
|
||||
} else {
|
||||
this.mainContainer.classList.remove(LayoutClasses.AUXILIARYBAR_HIDDEN);
|
||||
}
|
||||
|
||||
// Propagate to grid
|
||||
this.workbenchGrid.setViewVisible(
|
||||
this.auxiliaryBarPartView,
|
||||
!hidden,
|
||||
createViewVisibilityAnimation(hidden, () => {
|
||||
if (!hidden) { return; }
|
||||
// Deferred to after close animation
|
||||
this.mainContainer.classList.add(LayoutClasses.AUXILIARYBAR_HIDDEN);
|
||||
if (this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.AuxiliaryBar)) {
|
||||
this.paneCompositeService.hideActivePaneComposite(ViewContainerLocation.AuxiliaryBar);
|
||||
this.focusPanelOrEditor();
|
||||
}
|
||||
})
|
||||
);
|
||||
this.workbenchGrid.setViewVisible(this.auxiliaryBarPartView, !hidden);
|
||||
|
||||
// If auxiliary bar becomes hidden, also hide the current active pane composite if any
|
||||
if (hidden && this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.AuxiliaryBar)) {
|
||||
this.paneCompositeService.hideActivePaneComposite(ViewContainerLocation.AuxiliaryBar);
|
||||
this.focusPanelOrEditor();
|
||||
}
|
||||
|
||||
// If auxiliary bar becomes visible, show last active pane composite or default pane composite
|
||||
if (!hidden && !this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.AuxiliaryBar)) {
|
||||
else if (!hidden && !this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.AuxiliaryBar)) {
|
||||
let viewletToOpen: string | undefined = this.paneCompositeService.getLastActivePaneCompositeId(ViewContainerLocation.AuxiliaryBar);
|
||||
|
||||
// verify that the viewlet we try to open has views before we default to it
|
||||
@@ -2739,21 +2716,6 @@ function getZenModeConfiguration(configurationService: IConfigurationService): Z
|
||||
return configurationService.getValue<ZenModeConfiguration>(WorkbenchLayoutSettings.ZEN_MODE_CONFIG);
|
||||
}
|
||||
|
||||
/** Duration (ms) for panel/sidebar open (entrance) animations. */
|
||||
const PANEL_OPEN_DURATION = 135;
|
||||
|
||||
/** Duration (ms) for panel/sidebar close (exit) animations. */
|
||||
const PANEL_CLOSE_DURATION = 35;
|
||||
|
||||
function createViewVisibilityAnimation(hidden: boolean, onComplete?: () => void, token: CancellationToken = CancellationToken.None): IViewVisibilityAnimationOptions {
|
||||
return {
|
||||
duration: hidden ? PANEL_CLOSE_DURATION : PANEL_OPEN_DURATION,
|
||||
easing: hidden ? EASE_IN : EASE_OUT,
|
||||
token,
|
||||
onComplete,
|
||||
};
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Layout State Model
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* Motion custom properties -- only active when motion is enabled */
|
||||
.monaco-workbench.monaco-enable-motion {
|
||||
--vscode-motion-panel-open-duration: 175ms;
|
||||
--vscode-motion-panel-close-duration: 75ms;
|
||||
--vscode-motion-quick-input-open-duration: 175ms;
|
||||
--vscode-motion-quick-input-close-duration: 75ms;
|
||||
--vscode-motion-ease-out: cubic-bezier(0.1, 0.9, 0.2, 1);
|
||||
--vscode-motion-ease-in: cubic-bezier(0.9, 0.1, 1, 0.2);
|
||||
}
|
||||
|
||||
/* Disable all motion durations when reduced motion is active */
|
||||
.monaco-workbench.monaco-reduce-motion {
|
||||
--vscode-motion-panel-open-duration: 0ms;
|
||||
--vscode-motion-panel-close-duration: 0ms;
|
||||
--vscode-motion-quick-input-open-duration: 0ms;
|
||||
--vscode-motion-quick-input-close-duration: 0ms;
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import './media/style.css';
|
||||
import './media/motion.css';
|
||||
import { registerThemingParticipant } from '../../platform/theme/common/themeService.js';
|
||||
import { WORKBENCH_BACKGROUND, TITLE_BAR_ACTIVE_BACKGROUND } from '../common/theme.js';
|
||||
import { isWeb, isIOS } from '../../base/common/platform.js';
|
||||
|
||||
Reference in New Issue
Block a user