mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 17:19:48 +01:00
Gutter indicator menu redesign (#244056)
gutter indicator menu redesign
This commit is contained in:
committed by
GitHub
parent
c27b2c1fbb
commit
385dfa554c
+138
-34
@@ -8,22 +8,25 @@ import { renderIcon } from '../../../../../../../base/browser/ui/iconLabel/iconL
|
||||
import { Codicon } from '../../../../../../../base/common/codicons.js';
|
||||
import { BugIndicatingError } from '../../../../../../../base/common/errors.js';
|
||||
import { Disposable, DisposableStore, toDisposable } from '../../../../../../../base/common/lifecycle.js';
|
||||
import { IObservable, ISettableObservable, constObservable, derived, observableFromEvent, observableValue, runOnChange } from '../../../../../../../base/common/observable.js';
|
||||
import { IObservable, ISettableObservable, autorun, constObservable, derived, observableFromEvent, observableValue, runOnChange } from '../../../../../../../base/common/observable.js';
|
||||
import { debouncedObservable } from '../../../../../../../base/common/observableInternal/utils.js';
|
||||
import { IAccessibilityService } from '../../../../../../../platform/accessibility/common/accessibility.js';
|
||||
import { IHoverService } from '../../../../../../../platform/hover/browser/hover.js';
|
||||
import { IInstantiationService } from '../../../../../../../platform/instantiation/common/instantiation.js';
|
||||
import { asCssVariable } from '../../../../../../../platform/theme/common/colorUtils.js';
|
||||
import { IThemeService } from '../../../../../../../platform/theme/common/themeService.js';
|
||||
import { IEditorMouseEvent } from '../../../../../../browser/editorBrowser.js';
|
||||
import { ObservableCodeEditor } from '../../../../../../browser/observableCodeEditor.js';
|
||||
import { Point } from '../../../../../../browser/point.js';
|
||||
import { Rect } from '../../../../../../browser/rect.js';
|
||||
import { HoverService } from '../../../../../../browser/services/hoverService/hoverService.js';
|
||||
import { HoverWidget } from '../../../../../../browser/services/hoverService/hoverWidget.js';
|
||||
import { EditorOption } from '../../../../../../common/config/editorOptions.js';
|
||||
import { EditorOption, RenderLineNumbersType } from '../../../../../../common/config/editorOptions.js';
|
||||
import { LineRange } from '../../../../../../common/core/lineRange.js';
|
||||
import { OffsetRange } from '../../../../../../common/core/offsetRange.js';
|
||||
import { StickyScrollController } from '../../../../../stickyScroll/browser/stickyScrollController.js';
|
||||
import { IInlineEditModel, InlineEditTabAction } from '../inlineEditsViewInterface.js';
|
||||
import { inlineEditIndicatorBackground, inlineEditIndicatorPrimaryBackground, inlineEditIndicatorPrimaryForeground, inlineEditIndicatorSecondaryBackground, inlineEditIndicatorSecondaryForeground, inlineEditIndicatorsuccessfulBackground, inlineEditIndicatorsuccessfulForeground } from '../theme.js';
|
||||
import { getEditorBlendedColor, inlineEditIndicatorBackground, inlineEditIndicatorPrimaryBackground, inlineEditIndicatorPrimaryBorder, inlineEditIndicatorPrimaryForeground, inlineEditIndicatorSecondaryBackground, inlineEditIndicatorSecondaryBorder, inlineEditIndicatorSecondaryForeground, inlineEditIndicatorsuccessfulBackground, inlineEditIndicatorsuccessfulBorder, inlineEditIndicatorsuccessfulForeground } from '../theme.js';
|
||||
import { mapOutFalsy, rectToProps } from '../utils/utils.js';
|
||||
import { GutterIndicatorMenuContent } from './gutterIndicatorMenu.js';
|
||||
|
||||
@@ -35,8 +38,7 @@ export class InlineEditsGutterIndicator extends Disposable {
|
||||
return model;
|
||||
}
|
||||
|
||||
private readonly _gutterIndicatorBackgroundColor: IObservable<string>;
|
||||
private readonly _gutterIndicatorForegroundColor: IObservable<string>;
|
||||
private readonly _gutterIndicatorStyles: IObservable<{ background: string; foreground: string; border: string }>;
|
||||
private readonly _isHoveredOverInlineEditDebounced: IObservable<boolean>;
|
||||
|
||||
constructor(
|
||||
@@ -49,21 +51,27 @@ export class InlineEditsGutterIndicator extends Disposable {
|
||||
@IHoverService private readonly _hoverService: HoverService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IAccessibilityService accessibilityService: IAccessibilityService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._gutterIndicatorBackgroundColor = this._tabAction.map(v => {
|
||||
this._gutterIndicatorStyles = this._tabAction.map((v, reader) => {
|
||||
switch (v) {
|
||||
case InlineEditTabAction.Inactive: return asCssVariable(inlineEditIndicatorSecondaryBackground);
|
||||
case InlineEditTabAction.Jump: return asCssVariable(inlineEditIndicatorPrimaryBackground);
|
||||
case InlineEditTabAction.Accept: return asCssVariable(inlineEditIndicatorsuccessfulBackground);
|
||||
}
|
||||
});
|
||||
this._gutterIndicatorForegroundColor = this._tabAction.map(v => {
|
||||
switch (v) {
|
||||
case InlineEditTabAction.Inactive: return asCssVariable(inlineEditIndicatorSecondaryForeground);
|
||||
case InlineEditTabAction.Jump: return asCssVariable(inlineEditIndicatorPrimaryForeground);
|
||||
case InlineEditTabAction.Accept: return asCssVariable(inlineEditIndicatorsuccessfulForeground);
|
||||
case InlineEditTabAction.Inactive: return {
|
||||
background: getEditorBlendedColor(inlineEditIndicatorSecondaryBackground, themeService).read(reader).toString(),
|
||||
foreground: getEditorBlendedColor(inlineEditIndicatorSecondaryForeground, themeService).read(reader).toString(),
|
||||
border: getEditorBlendedColor(inlineEditIndicatorSecondaryBorder, themeService).read(reader).toString(),
|
||||
};
|
||||
case InlineEditTabAction.Jump: return {
|
||||
background: getEditorBlendedColor(inlineEditIndicatorPrimaryBackground, themeService).read(reader).toString(),
|
||||
foreground: getEditorBlendedColor(inlineEditIndicatorPrimaryForeground, themeService).read(reader).toString(),
|
||||
border: getEditorBlendedColor(inlineEditIndicatorPrimaryBorder, themeService).read(reader).toString()
|
||||
};
|
||||
case InlineEditTabAction.Accept: return {
|
||||
background: getEditorBlendedColor(inlineEditIndicatorsuccessfulBackground, themeService).read(reader).toString(),
|
||||
foreground: getEditorBlendedColor(inlineEditIndicatorsuccessfulForeground, themeService).read(reader).toString(),
|
||||
border: getEditorBlendedColor(inlineEditIndicatorsuccessfulBorder, themeService).read(reader).toString()
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -74,6 +82,18 @@ export class InlineEditsGutterIndicator extends Disposable {
|
||||
minContentWidthInPx: constObservable(0),
|
||||
}));
|
||||
|
||||
this._register(this._editorObs.editor.onMouseMove((e: IEditorMouseEvent) => {
|
||||
const el = this._iconRef.element;
|
||||
const rect = el.getBoundingClientRect();
|
||||
const rectangularArea = Rect.fromLeftTopWidthHeight(rect.left, rect.top, rect.width, rect.height);
|
||||
const point = new Point(e.event.posx, e.event.posy);
|
||||
this._isHoveredOverIcon.set(rectangularArea.containsPoint(point), undefined);
|
||||
}));
|
||||
|
||||
this._register(this._editorObs.editor.onDidScrollChange(() => {
|
||||
this._isHoveredOverIcon.set(false, undefined);
|
||||
}));
|
||||
|
||||
this._isHoveredOverInlineEditDebounced = debouncedObservable(this._isHoveringOverInlineEdit, 100);
|
||||
|
||||
if (!accessibilityService.isMotionReduced()) {
|
||||
@@ -95,7 +115,7 @@ export class InlineEditsGutterIndicator extends Disposable {
|
||||
// PULSE ANIMATION:
|
||||
this._iconRef.element.animate([
|
||||
{
|
||||
outline: `2px solid ${this._gutterIndicatorBackgroundColor.get()}`,
|
||||
outline: `2px solid ${this._gutterIndicatorStyles.map(v => v.border).get()}`,
|
||||
outlineOffset: '-1px',
|
||||
offset: 0
|
||||
},
|
||||
@@ -107,6 +127,13 @@ export class InlineEditsGutterIndicator extends Disposable {
|
||||
], { duration: 500 });
|
||||
}));
|
||||
}
|
||||
|
||||
this._register(autorun(reader => {
|
||||
this._indicator.readEffect(reader);
|
||||
if (this._indicator.element) {
|
||||
this._editorObs.editor.applyFontInfo(this._indicator.element);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private readonly _originalRangeObs = mapOutFalsy(this._originalRange);
|
||||
@@ -125,38 +152,95 @@ export class InlineEditsGutterIndicator extends Disposable {
|
||||
? observableFromEvent(this._stickyScrollController.onDidChangeStickyScrollHeight, () => this._stickyScrollController!.stickyScrollWidgetHeight)
|
||||
: constObservable(0);
|
||||
|
||||
private readonly _lineNumberToRender = derived(this, reader => {
|
||||
if (this._verticalOffset.read(reader) !== 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const lineNumber = this._originalRange.read(reader)?.startLineNumber;
|
||||
const lineNumberOptions = this._editorObs.getOption(EditorOption.lineNumbers).read(reader);
|
||||
|
||||
if (lineNumber === undefined || lineNumberOptions.renderType === RenderLineNumbersType.Off) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (lineNumberOptions.renderType === RenderLineNumbersType.Interval) {
|
||||
const cursorPosition = this._editorObs.cursorPosition.read(reader);
|
||||
if (lineNumber % 10 === 0 || cursorPosition && cursorPosition.lineNumber === lineNumber) {
|
||||
return lineNumber.toString();
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
if (lineNumberOptions.renderType === RenderLineNumbersType.Relative) {
|
||||
const cursorPosition = this._editorObs.cursorPosition.read(reader);
|
||||
if (!cursorPosition) {
|
||||
return '';
|
||||
}
|
||||
const relativeLineNumber = Math.abs(lineNumber - cursorPosition.lineNumber);
|
||||
if (relativeLineNumber === 0) {
|
||||
return lineNumber.toString();
|
||||
}
|
||||
return relativeLineNumber.toString();
|
||||
}
|
||||
|
||||
if (lineNumberOptions.renderType === RenderLineNumbersType.Custom) {
|
||||
if (lineNumberOptions.renderFn) {
|
||||
return lineNumberOptions.renderFn(lineNumber);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
return lineNumber.toString();
|
||||
});
|
||||
|
||||
private readonly _layout = derived(this, reader => {
|
||||
const s = this._state.read(reader);
|
||||
if (!s) { return undefined; }
|
||||
|
||||
const layout = this._editorObs.layoutInfo.read(reader);
|
||||
|
||||
const lineHeight = this._editorObs.getOption(EditorOption.lineHeight).read(reader);
|
||||
const bottomPadding = 1;
|
||||
const leftPadding = 1;
|
||||
const rightPadding = 1;
|
||||
|
||||
// Entire editor area without sticky scroll
|
||||
const fullViewPort = Rect.fromLeftTopRightBottom(0, 0, layout.width, layout.height - bottomPadding);
|
||||
const viewPortWithStickyScroll = fullViewPort.withTop(this._stickyScrollHeight.read(reader));
|
||||
|
||||
// The glyph margin area across all relevant lines
|
||||
const targetVertRange = s.lineOffsetRange.read(reader);
|
||||
const targetRect = Rect.fromRanges(OffsetRange.fromTo(leftPadding + layout.glyphMarginLeft, layout.decorationsLeft + layout.decorationsWidth - rightPadding), targetVertRange);
|
||||
|
||||
const space = 1;
|
||||
|
||||
const targetRect = Rect.fromRanges(OffsetRange.fromTo(space + layout.glyphMarginLeft, layout.lineNumbersLeft + layout.lineNumbersWidth + 4), targetVertRange);
|
||||
|
||||
|
||||
const lineHeight = this._editorObs.getOption(EditorOption.lineHeight).read(reader);
|
||||
// The gutter view container (pill)
|
||||
const pillOffset = this._verticalOffset.read(reader);
|
||||
const pillRect = targetRect.withHeight(lineHeight).withWidth(22).translateY(pillOffset);
|
||||
let pillRect = targetRect.withHeight(lineHeight).withWidth(22).translateY(pillOffset);
|
||||
const pillRectMoved = pillRect.moveToBeContainedIn(viewPortWithStickyScroll);
|
||||
|
||||
const rect = targetRect;
|
||||
|
||||
const iconRect = (targetRect.containsRect(pillRectMoved))
|
||||
// Move pill to be in viewport if it is not
|
||||
pillRect = (targetRect.containsRect(pillRectMoved))
|
||||
? pillRectMoved
|
||||
: pillRectMoved.moveToBeContainedIn(fullViewPort.intersect(targetRect.union(fullViewPort.withHeight(lineHeight)))!); //viewPortWithStickyScroll.intersect(rect)!;
|
||||
|
||||
// docked = pill was already in the viewport
|
||||
const docked = rect.containsRect(pillRect) && viewPortWithStickyScroll.containsRect(pillRect);
|
||||
let iconDirecion = targetRect.containsRect(pillRect) ?
|
||||
'right' as const
|
||||
: pillRect.top > targetRect.top ?
|
||||
'top' as const :
|
||||
'bottom' as const;
|
||||
|
||||
const docked = rect.containsRect(iconRect) && viewPortWithStickyScroll.containsRect(iconRect);
|
||||
let iconDirecion = (targetRect.containsRect(iconRect) ? 'right' as const
|
||||
: iconRect.top > targetRect.top ? 'top' as const : 'bottom' as const);
|
||||
// Grow icon the the whole glyph margin area if it is docked
|
||||
let lineNumberRect = pillRect.withWidth(0);
|
||||
let iconRect = pillRect;
|
||||
if (docked && pillRect.top === targetRect.top + pillOffset) {
|
||||
pillRect = pillRect.withWidth(layout.decorationsLeft + layout.decorationsWidth - layout.glyphMarginLeft - leftPadding - rightPadding);
|
||||
lineNumberRect = pillRect.intersectHorizontal(new OffsetRange(0, layout.lineNumbersLeft + layout.lineNumbersWidth - leftPadding - 1));
|
||||
iconRect = iconRect.translateX(lineNumberRect.width);
|
||||
}
|
||||
|
||||
let icon;
|
||||
if (docked && (this._isHoveredOverIconDebounced.read(reader) || this._isHoveredOverInlineEditDebounced.read(reader))) {
|
||||
@@ -179,6 +263,9 @@ export class InlineEditsGutterIndicator extends Disposable {
|
||||
rotation,
|
||||
docked,
|
||||
iconRect,
|
||||
pillRect,
|
||||
lineHeight,
|
||||
lineNumberRect,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -217,6 +304,7 @@ export class InlineEditsGutterIndicator extends Disposable {
|
||||
}) as HoverWidget | undefined;
|
||||
if (h) {
|
||||
this._hoverVisible.set(true, undefined);
|
||||
disposableStore.add(this._editorObs.editor.onDidScrollChange(() => h.dispose()));
|
||||
disposableStore.add(h.onDispose(() => {
|
||||
this._hoverVisible.set(false, undefined);
|
||||
disposableStore.dispose();
|
||||
@@ -262,23 +350,37 @@ export class InlineEditsGutterIndicator extends Disposable {
|
||||
ref: this._iconRef,
|
||||
onmouseenter: () => {
|
||||
// TODO show hover when hovering ghost text etc.
|
||||
this._isHoveredOverIcon.set(true, undefined);
|
||||
this._showHover();
|
||||
},
|
||||
onmouseleave: () => { this._isHoveredOverIcon.set(false, undefined); },
|
||||
style: {
|
||||
cursor: 'pointer',
|
||||
zIndex: '1000',
|
||||
position: 'absolute',
|
||||
backgroundColor: this._gutterIndicatorBackgroundColor,
|
||||
['--vscodeIconForeground' as any]: this._gutterIndicatorForegroundColor,
|
||||
backgroundColor: this._gutterIndicatorStyles.map(v => v.background),
|
||||
['--vscodeIconForeground' as any]: this._gutterIndicatorStyles.map(v => v.foreground),
|
||||
border: this._gutterIndicatorStyles.map(v => `1px solid ${v.border}`),
|
||||
boxSizing: 'border-box',
|
||||
borderRadius: '4px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
transition: 'background-color 0.2s ease-in-out',
|
||||
...rectToProps(reader => layout.read(reader).iconRect),
|
||||
transition: 'background-color 0.2s ease-in-out, width 0.2s ease-in-out',
|
||||
...rectToProps(reader => layout.read(reader).pillRect),
|
||||
}
|
||||
}, [
|
||||
n.div({
|
||||
className: 'line-number',
|
||||
style: {
|
||||
lineHeight: layout.map(l => `${l.lineHeight}px`),
|
||||
display: layout.map(l => l.lineNumberRect.width > 0 ? 'flex' : 'none'),
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
width: layout.map(l => l.lineNumberRect.width),
|
||||
height: '100%',
|
||||
color: this._gutterIndicatorStyles.map(v => v.foreground),
|
||||
}
|
||||
},
|
||||
this._lineNumberToRender
|
||||
),
|
||||
n.div({
|
||||
style: {
|
||||
rotate: layout.map(i => `${i.rotation}deg`),
|
||||
@@ -286,6 +388,8 @@ export class InlineEditsGutterIndicator extends Disposable {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '100%',
|
||||
width: layout.map(l => `${l.iconRect.width}px`),
|
||||
}
|
||||
}, [
|
||||
layout.map(i => i.icon),
|
||||
|
||||
@@ -66,9 +66,19 @@ export const inlineEditIndicatorPrimaryForeground = registerColor(
|
||||
buttonForeground,
|
||||
localize('inlineEdit.gutterIndicator.primaryForeground', 'Foreground color for the primary inline edit gutter indicator.')
|
||||
);
|
||||
export const inlineEditIndicatorPrimaryBorder = registerColor(
|
||||
'inlineEdit.gutterIndicator.primaryBorder',
|
||||
buttonBackground,
|
||||
localize('inlineEdit.gutterIndicator.primaryBorder', 'Border color for the primary inline edit gutter indicator.')
|
||||
);
|
||||
export const inlineEditIndicatorPrimaryBackground = registerColor(
|
||||
'inlineEdit.gutterIndicator.primaryBackground',
|
||||
buttonBackground,
|
||||
{
|
||||
light: transparent(inlineEditIndicatorPrimaryBorder, 0.5),
|
||||
dark: transparent(inlineEditIndicatorPrimaryBorder, 0.4),
|
||||
hcDark: transparent(inlineEditIndicatorPrimaryBorder, 0.4),
|
||||
hcLight: transparent(inlineEditIndicatorPrimaryBorder, 0.5),
|
||||
},
|
||||
localize('inlineEdit.gutterIndicator.primaryBackground', 'Background color for the primary inline edit gutter indicator.')
|
||||
);
|
||||
|
||||
@@ -77,9 +87,14 @@ export const inlineEditIndicatorSecondaryForeground = registerColor(
|
||||
buttonSecondaryForeground,
|
||||
localize('inlineEdit.gutterIndicator.secondaryForeground', 'Foreground color for the secondary inline edit gutter indicator.')
|
||||
);
|
||||
export const inlineEditIndicatorSecondaryBorder = registerColor(
|
||||
'inlineEdit.gutterIndicator.secondaryBorder',
|
||||
buttonSecondaryBackground,
|
||||
localize('inlineEdit.gutterIndicator.secondaryBorder', 'Border color for the secondary inline edit gutter indicator.')
|
||||
);
|
||||
export const inlineEditIndicatorSecondaryBackground = registerColor(
|
||||
'inlineEdit.gutterIndicator.secondaryBackground',
|
||||
buttonSecondaryBackground,
|
||||
inlineEditIndicatorSecondaryBorder,
|
||||
localize('inlineEdit.gutterIndicator.secondaryBackground', 'Background color for the secondary inline edit gutter indicator.')
|
||||
);
|
||||
|
||||
@@ -88,9 +103,14 @@ export const inlineEditIndicatorsuccessfulForeground = registerColor(
|
||||
buttonForeground,
|
||||
localize('inlineEdit.gutterIndicator.successfulForeground', 'Foreground color for the successful inline edit gutter indicator.')
|
||||
);
|
||||
export const inlineEditIndicatorsuccessfulBorder = registerColor(
|
||||
'inlineEdit.gutterIndicator.successfulBorder',
|
||||
buttonBackground,
|
||||
localize('inlineEdit.gutterIndicator.successfulBorder', 'Border color for the successful inline edit gutter indicator.')
|
||||
);
|
||||
export const inlineEditIndicatorsuccessfulBackground = registerColor(
|
||||
'inlineEdit.gutterIndicator.successfulBackground',
|
||||
{ light: '#2e825c', dark: '#2e825c', hcLight: '#2e825c', hcDark: '#2e825c' },
|
||||
inlineEditIndicatorsuccessfulBorder,
|
||||
localize('inlineEdit.gutterIndicator.successfulBackground', 'Background color for the successful inline edit gutter indicator.')
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user