Skip to content

Commit

Permalink
Rebase against the upstream 2327683
Browse files Browse the repository at this point in the history
vscode-upstream-sha1: 2327683
  • Loading branch information
Eclipse Che Sync committed Jan 9, 2025
2 parents ecfbdc2 + 2327683 commit 8f89921
Show file tree
Hide file tree
Showing 2 changed files with 236 additions and 5 deletions.
28 changes: 27 additions & 1 deletion code/src/vs/platform/quickinput/browser/media/quickInput.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
width: 600px;
z-index: 2550;
left: 50%;
margin-left: -300px;
-webkit-app-region: no-drag;
border-radius: 6px;
}

.quick-input-titlebar {
cursor: grab;
display: flex;
align-items: center;
border-top-right-radius: 5px;
Expand All @@ -37,6 +37,7 @@
}

.quick-input-title {
cursor: grab;
padding: 3px 0px;
text-align: center;
text-overflow: ellipsis;
Expand Down Expand Up @@ -69,6 +70,7 @@
}

.quick-input-header {
cursor: grab;
display: flex;
padding: 8px 6px 2px 6px;
}
Expand Down Expand Up @@ -362,3 +364,27 @@
.quick-input-list .monaco-tl-twistie {
display: none !important;
}

/* Quick input snap lines visible while DnD */
.quick-input-widget-snapline {
position: absolute;
z-index: 2549;
}

.quick-input-widget-snapline.hidden {
display: none;
}

.quick-input-widget-snapline.horizontal {
border-top: 1px dashed var(--vscode-editorRuler-foreground);
height: 0;
width: 100%;
left: 0;
}

.quick-input-widget-snapline.vertical {
border-left: 1px dashed var(--vscode-editorRuler-foreground);
height: 100%;
width: 0;
top: 0;
}
213 changes: 209 additions & 4 deletions code/src/vs/platform/quickinput/browser/quickInputController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,19 @@ import { IInstantiationService } from '../../instantiation/common/instantiation.
import { QuickInputTree } from './quickInputTree.js';
import { IContextKeyService } from '../../contextkey/common/contextkey.js';
import './quickInputActions.js';
import { autorun, observableValue } from '../../../base/common/observable.js';
import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js';
import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js';

const $ = dom.$;

const VIEWSTATE_STORAGE_KEY = 'workbench.quickInput.viewState';

type QuickInputViewState = {
readonly top?: number;
readonly left?: number;
};

export class QuickInputController extends Disposable {
private static readonly MAX_WIDTH = 600; // Max total width of quick input widget

Expand Down Expand Up @@ -58,6 +68,9 @@ export class QuickInputController extends Disposable {

private previousFocusElement?: HTMLElement;

private viewState: QuickInputViewState | undefined;
private dndController: QuickInputDragAndDropController | undefined;

private readonly inQuickInputContext = InQuickInputContextKey.bindTo(this.contextKeyService);
private readonly quickInputTypeContext = QuickInputTypeContextKey.bindTo(this.contextKeyService);
private readonly endOfQuickInputBoxContext = EndOfQuickInputBoxContextKey.bindTo(this.contextKeyService);
Expand All @@ -66,7 +79,8 @@ export class QuickInputController extends Disposable {
private options: IQuickInputOptions,
@ILayoutService private readonly layoutService: ILayoutService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IContextKeyService private readonly contextKeyService: IContextKeyService
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IStorageService private readonly storageService: IStorageService
) {
super();
this.idPrefix = options.idPrefix;
Expand All @@ -83,6 +97,7 @@ export class QuickInputController extends Disposable {
this.layout(this.layoutService.mainContainerDimension, this.layoutService.mainContainerOffset.quickPickTop);
}
}));
this.viewState = this.loadViewState();
}

private registerKeyModsListeners(window: Window, disposables: DisposableStore): void {
Expand Down Expand Up @@ -314,6 +329,36 @@ export class QuickInputController extends Disposable {
}
}));

// Drag and Drop support
this.dndController = this._register(this.instantiationService.createInstance(
QuickInputDragAndDropController, this._container, container, [titleBar, title, headerContainer]));

// DnD update layout
this._register(autorun(reader => {
const dndViewState = this.dndController?.dndViewState.read(reader);
if (!dndViewState) {
return;
}

if (dndViewState.top !== undefined && dndViewState.left !== undefined) {
this.viewState = {
...this.viewState,
top: dndViewState.top,
left: dndViewState.left
};
} else {
// Reset position/size
this.viewState = undefined;
}

this.updateLayout();

// Save position
if (dndViewState.done) {
this.saveViewState(this.viewState);
}
}));

this.ui = {
container,
styleSheet,
Expand Down Expand Up @@ -360,6 +405,7 @@ export class QuickInputController extends Disposable {
if (this.ui) {
this._container = container;
dom.append(this._container, this.ui.container);
this.dndController?.reparentUI(this._container);
}
}

Expand Down Expand Up @@ -729,12 +775,13 @@ export class QuickInputController extends Disposable {

private updateLayout() {
if (this.ui && this.isVisible()) {
this.ui.container.style.top = `${this.titleBarOffset}px`;

const style = this.ui.container.style;
const width = Math.min(this.dimension!.width * 0.62 /* golden cut */, QuickInputController.MAX_WIDTH);
style.width = width + 'px';
style.marginLeft = '-' + (width / 2) + 'px';

// Position
style.top = `${this.viewState?.top ? Math.round(this.dimension!.height * this.viewState.top) : this.titleBarOffset}px`;
style.left = `${Math.round((this.dimension!.width * (this.viewState?.left ?? 0.5 /* center */)) - (width / 2))}px`;

this.ui.inputBox.layout();
this.ui.list.layout(this.dimension && this.dimension.height * 0.4);
Expand Down Expand Up @@ -800,6 +847,164 @@ export class QuickInputController extends Disposable {
}
}
}

private loadViewState(): QuickInputViewState | undefined {
try {
const data = JSON.parse(this.storageService.get(VIEWSTATE_STORAGE_KEY, StorageScope.APPLICATION, '{}'));
if (data.top !== undefined || data.left !== undefined) {
return data;
}
} catch { }

return undefined;
}

private saveViewState(viewState: QuickInputViewState | undefined): void {
const isMainWindow = this.layoutService.activeContainer === this.layoutService.mainContainer;
if (!isMainWindow) {
return;
}

if (viewState !== undefined) {
this.storageService.store(VIEWSTATE_STORAGE_KEY, JSON.stringify(viewState), StorageScope.APPLICATION, StorageTarget.MACHINE);
} else {
this.storageService.remove(VIEWSTATE_STORAGE_KEY, StorageScope.APPLICATION);
}
}
}

export interface IQuickInputControllerHost extends ILayoutService { }

class QuickInputDragAndDropController extends Disposable {
readonly dndViewState = observableValue<{ top?: number; left?: number; done: boolean } | undefined>(this, undefined);

private readonly _snapThreshold = 20;
private readonly _snapLineHorizontalRatio = 0.15;
private readonly _snapLineHorizontal: HTMLElement;
private readonly _snapLineVertical1: HTMLElement;
private readonly _snapLineVertical2: HTMLElement;

constructor(
private _container: HTMLElement,
private readonly _quickInputContainer: HTMLElement,
private _quickInputDragAreas: HTMLElement[],
@ILayoutService private readonly _layoutService: ILayoutService
) {
super();

this._snapLineHorizontal = dom.append(this._container, $('.quick-input-widget-snapline.horizontal.hidden'));
this._snapLineVertical1 = dom.append(this._container, $('.quick-input-widget-snapline.vertical.hidden'));
this._snapLineVertical2 = dom.append(this._container, $('.quick-input-widget-snapline.vertical.hidden'));

this.registerMouseListeners();
}

reparentUI(container: HTMLElement): void {
this._container = container;
this._snapLineHorizontal.remove();
this._snapLineVertical1.remove();
this._snapLineVertical2.remove();
dom.append(this._container, this._snapLineHorizontal);
dom.append(this._container, this._snapLineVertical1);
dom.append(this._container, this._snapLineVertical2);
}

private registerMouseListeners(): void {
for (const dragArea of this._quickInputDragAreas) {
let top: number | undefined;
let left: number | undefined;

// Double click
this._register(dom.addDisposableGenericMouseUpListener(dragArea, (event: MouseEvent) => {
const originEvent = new StandardMouseEvent(dom.getWindow(dragArea), event);

// Ignore event if the target is not the drag area
if (originEvent.target !== dragArea) {
return;
}

if (originEvent.detail === 2) {
top = undefined;
left = undefined;

this.dndViewState.set({ top, left, done: true }, undefined);
}
}));

// Mouse down
this._register(dom.addDisposableGenericMouseDownListener(dragArea, (e: MouseEvent) => {
const activeWindow = dom.getWindow(this._layoutService.activeContainer);
const originEvent = new StandardMouseEvent(activeWindow, e);

// Ignore event if the target is not the drag area
if (originEvent.target !== dragArea) {
return;
}

// Mouse position offset relative to dragArea
const dragAreaRect = this._quickInputContainer.getBoundingClientRect();
const dragOffsetX = originEvent.browserEvent.clientX - dragAreaRect.left;
const dragOffsetY = originEvent.browserEvent.clientY - dragAreaRect.top;

// Snap lines
let snapLinesVisible = false;
const snapCoordinateYTop = this._layoutService.activeContainerOffset.quickPickTop;
const snapCoordinateY = Math.round(this._container.clientHeight * this._snapLineHorizontalRatio);
const snapCoordinateX = Math.round(this._container.clientWidth / 2) - Math.round(this._quickInputContainer.clientWidth / 2);

// Mouse move
const mouseMoveListener = dom.addDisposableGenericMouseMoveListener(activeWindow, (e: MouseEvent) => {
const mouseMoveEvent = new StandardMouseEvent(activeWindow, e);
mouseMoveEvent.preventDefault();

if (!snapLinesVisible) {
this._showSnapLines(snapCoordinateY, snapCoordinateX);
snapLinesVisible = true;
}

let topCoordinate = e.clientY - dragOffsetY;
topCoordinate = Math.max(0, Math.min(topCoordinate, this._container.clientHeight - this._quickInputContainer.clientHeight));
topCoordinate = Math.abs(topCoordinate - snapCoordinateYTop) < this._snapThreshold ? snapCoordinateYTop : topCoordinate;
topCoordinate = Math.abs(topCoordinate - snapCoordinateY) < this._snapThreshold ? snapCoordinateY : topCoordinate;
top = topCoordinate / this._container.clientHeight;

let leftCoordinate = e.clientX - dragOffsetX;
leftCoordinate = Math.max(0, Math.min(leftCoordinate, this._container.clientWidth - this._quickInputContainer.clientWidth));
leftCoordinate = Math.abs(leftCoordinate - snapCoordinateX) < this._snapThreshold ? snapCoordinateX : leftCoordinate;
left = (leftCoordinate + (this._quickInputContainer.clientWidth / 2)) / this._container.clientWidth;

this.dndViewState.set({ top, left, done: false }, undefined);
});

// Mouse up
const mouseUpListener = dom.addDisposableGenericMouseUpListener(activeWindow, (e: MouseEvent) => {
// Hide snaplines
this._hideSnapLines();

// Save position
this.dndViewState.set({ top, left, done: true }, undefined);

// Dispose listeners
mouseMoveListener.dispose();
mouseUpListener.dispose();
});
}));
}
}

private _showSnapLines(horizontal: number, vertical: number) {
this._snapLineHorizontal.style.top = `${horizontal}px`;
this._snapLineVertical1.style.left = `${vertical}px`;
this._snapLineVertical2.style.left = `${vertical + this._quickInputContainer.clientWidth}px`;

this._snapLineHorizontal.classList.remove('hidden');
this._snapLineVertical1.classList.remove('hidden');
this._snapLineVertical2.classList.remove('hidden');
}

private _hideSnapLines() {
this._snapLineHorizontal.classList.add('hidden');
this._snapLineVertical1.classList.add('hidden');
this._snapLineVertical2.classList.add('hidden');
}
}

0 comments on commit 8f89921

Please sign in to comment.