diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts index f667738b748..ef9b1ba2462 100644 --- a/src/vs/base/browser/ui/grid/grid.ts +++ b/src/vs/base/browser/ui/grid/grid.ts @@ -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 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); } /** diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts index 03c416cfd89..112d7231f1a 100644 --- a/src/vs/base/browser/ui/grid/gridview.ts +++ b/src/vs/base/browser/ui/grid/gridview.ts @@ -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, 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, 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); } /** diff --git a/src/vs/base/browser/ui/motion/motion.css b/src/vs/base/browser/ui/motion/motion.css deleted file mode 100644 index 69e257be2d3..00000000000 --- a/src/vs/base/browser/ui/motion/motion.css +++ /dev/null @@ -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; -} diff --git a/src/vs/base/browser/ui/motion/motion.ts b/src/vs/base/browser/ui/motion/motion.ts deleted file mode 100644 index c2e8a045d41..00000000000 --- a/src/vs/base/browser/ui/motion/motion.ts +++ /dev/null @@ -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 diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 2c72bd8d869..35f2724c1a8 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -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= 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 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. * diff --git a/src/vs/platform/quickinput/browser/media/quickInput.css b/src/vs/platform/quickinput/browser/media/quickInput.css index 9afd0964022..bd509719a3c 100644 --- a/src/vs/platform/quickinput/browser/media/quickInput.css +++ b/src/vs/platform/quickinput/browser/media/quickInput.css @@ -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; -} diff --git a/src/vs/platform/quickinput/browser/quickInputController.ts b/src/vs/platform/quickinput/browser/quickInputController.ts index 11964ea5a30..8e5283ef9ad 100644 --- a/src/vs/platform/quickinput/browser/quickInputController.ts +++ b/src/vs/platform/quickinput/browser/quickInputController.ts @@ -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; private readonly quickInputTypeContext: IContextKey; @@ -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; diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index c7679ab51cb..b51e004b045 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -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(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 diff --git a/src/vs/workbench/browser/media/motion.css b/src/vs/workbench/browser/media/motion.css deleted file mode 100644 index fbd8215265a..00000000000 --- a/src/vs/workbench/browser/media/motion.css +++ /dev/null @@ -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; -} diff --git a/src/vs/workbench/browser/style.ts b/src/vs/workbench/browser/style.ts index de344e7e46b..9250ef3f280 100644 --- a/src/vs/workbench/browser/style.ts +++ b/src/vs/workbench/browser/style.ts @@ -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';