未验证 提交 2c0ff7ab 编写于 作者: I Isidor Nikolic 提交者: GitHub

Merge pull request #97072 from microsoft/isidorn/statusbar-focus

statusBar: focusNext and focusPrevious
......@@ -22,7 +22,7 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { isThemeColor } from 'vs/editor/common/editorCommon';
import { Color } from 'vs/base/common/color';
import { addClass, EventHelper, createStyleSheet, addDisposableListener, addClasses, removeClass, EventType, hide, show, removeClasses } from 'vs/base/browser/dom';
import { addClass, EventHelper, createStyleSheet, addDisposableListener, addClasses, removeClass, EventType, hide, show, removeClasses, isAncestor } from 'vs/base/browser/dom';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage';
import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
......@@ -35,6 +35,11 @@ import { assertIsDefined } from 'vs/base/common/types';
import { Emitter } from 'vs/base/common/event';
import { Command } from 'vs/editor/common/modes';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
interface IPendingStatusbarEntry {
id: string;
......@@ -51,8 +56,11 @@ interface IStatusbarViewModelEntry {
alignment: StatusbarAlignment;
priority: number;
container: HTMLElement;
labelContainer: HTMLElement;
}
const CONTEXT_STATUS_BAR_FOCUSED = new RawContextKey<boolean>('statusBarFocused', false);
class StatusbarViewModel extends Disposable {
static readonly HIDDEN_ENTRIES_KEY = 'workbench.statusbar.hidden';
......@@ -188,6 +196,40 @@ class StatusbarViewModel extends Disposable {
return this._entries.filter(entry => entry.alignment === alignment);
}
focusNextEntry(): void {
this.focusEntry(+1, 0);
}
focusPreviousEntry(): void {
this.focusEntry(-1, this.entries.length - 1);
}
private focusEntry(delta: number, restartPosition: number): void {
const getVisibleEntry = (start: number) => {
let indexToFocus = start;
let entry = (indexToFocus >= 0 && indexToFocus < this._entries.length) ? this._entries[indexToFocus] : undefined;
while (entry && this.isHidden(entry.id)) {
indexToFocus += delta;
entry = (indexToFocus >= 0 && indexToFocus < this._entries.length) ? this._entries[indexToFocus] : undefined;
}
return entry;
};
const focused = this._entries.find(entry => isAncestor(document.activeElement, entry.container));
if (focused) {
const entry = getVisibleEntry(this._entries.indexOf(focused) + delta);
if (entry) {
entry.labelContainer.focus();
return;
}
}
const entry = getVisibleEntry(restartPosition);
if (entry) {
entry.labelContainer.focus();
}
}
private updateVisibility(id: string, trigger: boolean): void;
private updateVisibility(entry: IStatusbarViewModelEntry, trigger: boolean): void;
private updateVisibility(arg1: string | IStatusbarViewModelEntry, trigger: boolean): void {
......@@ -355,6 +397,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
@IStorageService private readonly storageService: IStorageService,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@IContextMenuService private contextMenuService: IContextMenuService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
) {
super(Parts.STATUSBAR_PART, { hasTitle: false }, themeService, storageService, layoutService);
......@@ -415,7 +458,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
this.appendOneStatusbarEntry(itemContainer, alignment, priority);
// Add to view model
const viewModelEntry: IStatusbarViewModelEntry = { id, name, alignment, priority, container: itemContainer };
const viewModelEntry: IStatusbarViewModelEntry = { id, name, alignment, priority, container: itemContainer, labelContainer: item.labelContainer };
const viewModelEntryDispose = this.viewModel.add(viewModelEntry);
return {
......@@ -442,9 +485,21 @@ export class StatusbarPart extends Part implements IStatusbarService {
}
}
focusNextEntry(): void {
this.viewModel.focusNextEntry();
}
focusPreviousEntry(): void {
this.viewModel.focusPreviousEntry();
}
createContentArea(parent: HTMLElement): HTMLElement {
this.element = parent;
// Track focus within container
const scopedContextKeyService = this.contextKeyService.createScoped(this.element);
CONTEXT_STATUS_BAR_FOCUSED.bindTo(scopedContextKeyService).set(true);
// Left items container
this.leftItemsContainer = document.createElement('div');
addClasses(this.leftItemsContainer, 'left-items', 'items-container');
......@@ -645,13 +700,14 @@ class StatusbarEntryItem extends Disposable {
private entry!: IStatusbarEntry;
private labelContainer!: HTMLElement;
labelContainer!: HTMLElement;
private label!: CodiconLabel;
private readonly foregroundListener = this._register(new MutableDisposable());
private readonly backgroundListener = this._register(new MutableDisposable());
private readonly commandListener = this._register(new MutableDisposable());
private readonly commandMouseListener = this._register(new MutableDisposable());
private readonly commandKeyboardListener = this._register(new MutableDisposable());
constructor(
private container: HTMLElement,
......@@ -711,11 +767,18 @@ class StatusbarEntryItem extends Disposable {
// Update: Command
if (!this.entry || entry.command !== this.entry.command) {
this.commandListener.clear();
this.commandMouseListener.clear();
this.commandKeyboardListener.clear();
const command = entry.command;
if (command) {
this.commandListener.value = addDisposableListener(this.labelContainer, EventType.CLICK, () => this.executeCommand(command));
this.commandMouseListener.value = addDisposableListener(this.labelContainer, EventType.CLICK, () => this.executeCommand(command));
this.commandKeyboardListener.value = addDisposableListener(this.labelContainer, EventType.KEY_UP, e => {
const event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.Space) || event.equals(KeyCode.Enter)) {
this.executeCommand(command);
}
});
removeClass(this.labelContainer, 'disabled');
} else {
......@@ -814,7 +877,8 @@ class StatusbarEntryItem extends Disposable {
dispose(this.foregroundListener);
dispose(this.backgroundListener);
dispose(this.commandListener);
dispose(this.commandMouseListener);
dispose(this.commandKeyboardListener);
}
}
......@@ -822,6 +886,7 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
const statusBarItemHoverBackground = theme.getColor(STATUS_BAR_ITEM_HOVER_BACKGROUND);
if (statusBarItemHoverBackground) {
collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item a:hover { background-color: ${statusBarItemHoverBackground}; }`);
collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item a:focus { background-color: ${statusBarItemHoverBackground}; }`);
}
const statusBarItemActiveBackground = theme.getColor(STATUS_BAR_ITEM_ACTIVE_BACKGROUND);
......@@ -846,3 +911,27 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
});
registerSingleton(IStatusbarService, StatusbarPart);
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.statusBar.focusPrevious',
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyCode.LeftArrow,
secondary: [KeyCode.UpArrow],
when: CONTEXT_STATUS_BAR_FOCUSED,
handler: (accessor: ServicesAccessor) => {
const statusBarService = accessor.get(IStatusbarService);
statusBarService.focusPreviousEntry();
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.statusBar.focusNext',
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyCode.RightArrow,
secondary: [KeyCode.DownArrow],
when: CONTEXT_STATUS_BAR_FOCUSED,
handler: (accessor: ServicesAccessor) => {
const statusBarService = accessor.get(IStatusbarService);
statusBarService.focusNextEntry();
}
});
......@@ -89,6 +89,16 @@ export interface IStatusbarService {
* Allows to update an entry's visibility with the provided ID.
*/
updateEntryVisibility(id: string, visible: boolean): void;
/**
* Focuses the next status bar entry. If none focused, focuses the first.
*/
focusNextEntry(): void;
/**
* Focuses the previous status bar entry. If none focused, focuses the last.
*/
focusPreviousEntry(): void;
}
export interface IStatusbarEntryAccessor extends IDisposable {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册