diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index eb4c2bcc7f2ad91a7ca5dc3ae8067d80706e6107..03874f9ce788eeb3e4b71266cc639c82a2f406e1 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -8,7 +8,7 @@ import 'vs/css!./media/editorstatus'; import nls = require('vs/nls'); import { TPromise } from 'vs/base/common/winjs.base'; -import { $, append, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import { $, append, runAtThisOrScheduleAtNextAnimationFrame, addDisposableListener } from 'vs/base/browser/dom'; import strings = require('vs/base/common/strings'); import paths = require('vs/base/common/paths'); import types = require('vs/base/common/types'); @@ -17,6 +17,7 @@ import errors = require('vs/base/common/errors'); import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; import { Action } from 'vs/base/common/actions'; import { language, LANGUAGE_DEFAULT, AccessibilitySupport } from 'vs/base/common/platform'; +import * as browser from 'vs/base/browser/browser'; import { IMode } from 'vs/editor/common/modes'; import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput } from 'vs/workbench/common/editor'; @@ -45,8 +46,12 @@ import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { getCodeEditor as getEditorWidget, getCodeOrDiffEditor } from 'vs/editor/common/services/codeEditorService'; import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; -import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions'; +import { IConfigurationChangedEvent, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { attachStylerCallback } from 'vs/platform/theme/common/styler'; +import { widgetShadow, editorWidgetBackground } from 'vs/platform/theme/common/colorRegistry'; // TODO@Sandeep layer breaker // tslint:disable-next-line:import-patterns @@ -269,7 +274,8 @@ export class EditorStatus implements IStatusbarItem { @IInstantiationService private instantiationService: IInstantiationService, @IUntitledEditorService private untitledEditorService: IUntitledEditorService, @IModeService private modeService: IModeService, - @ITextFileService private textFileService: ITextFileService + @ITextFileService private textFileService: ITextFileService, + @IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService, ) { this.toDispose = []; this.activeEditorListeners = []; @@ -288,6 +294,7 @@ export class EditorStatus implements IStatusbarItem { this.screenRedearModeElement = append(this.element, $('a.editor-status-screenreadermode.status-bar-info')); this.screenRedearModeElement.textContent = nlsScreenReaderDetected; this.screenRedearModeElement.title = nlsScreenReaderDetectedTitle; + this.screenRedearModeElement.onclick = () => this.onScreenReaderModeClick(); hide(this.screenRedearModeElement); this.selectionElement = append(this.element, $('a.editor-status-selection')); @@ -469,6 +476,10 @@ export class EditorStatus implements IStatusbarItem { action.dispose(); } + private onScreenReaderModeClick(): void { + this.instantiationService.createInstance(ScreenReaderDetectedExplanation, this.screenRedearModeElement); + } + private onSelectionClick(): void { this.quickOpenService.show(':'); // "Go to line" } @@ -607,11 +618,26 @@ export class EditorStatus implements IStatusbarItem { this.updateState(update); } + private _promptedScreenReader: boolean = false; + private onScreenReaderModeChange(editorWidget: ICommonCodeEditor): void { let screenReaderMode = false; // We only support text based editors if (editorWidget) { + const screenReaderDetected = (browser.getAccessibilitySupport() === AccessibilitySupport.Enabled); + if (screenReaderDetected) { + const screenReaderConfiguration = this.configurationService.getConfiguration('editor').accessibilitySupport; + if (screenReaderConfiguration === 'auto') { + // show explanation + if (!this._promptedScreenReader) { + this._promptedScreenReader = true; + setTimeout(() => { + this.onScreenReaderModeClick(); + }, 100); + } + } + } screenReaderMode = (editorWidget.getConfiguration().accessibilitySupport === AccessibilitySupport.Enabled); } @@ -1184,3 +1210,103 @@ export class ChangeEncodingAction extends Action { }); } } + +class ScreenReaderDetectedExplanation { + + private toDispose: IDisposable[]; + + constructor( + anchorElement: HTMLElement, + @IThemeService private readonly themeService: IThemeService, + @IContextViewService private readonly contextViewService: IContextViewService, + @IConfigurationEditingService private readonly configurationEditingService: IConfigurationEditingService, + ) { + this.toDispose = []; + + this.contextViewService.showContextView({ + getAnchor: () => anchorElement, + + render: (container) => { + return this.renderContents(container); + }, + + onDOMEvent: (e, activeElement) => { + }, + + onHide: () => { + this.dispose(); + } + }); + } + + public dispose(): void { + this.toDispose = dispose(this.toDispose); + } + + protected renderContents(container: HTMLElement): IDisposable { + const domNode = $('div.screen-reader-detected-explanation', { + 'aria-hidden': 'true' + }); + + const title = $('h2.title', {}, nls.localize('screenReaderDetectedExplanation.title', "Screen Reader Detected")); + domNode.appendChild(title); + + const closeBtn = $('div.cancel'); + this.toDispose.push(addDisposableListener(closeBtn, 'click', () => { + this.contextViewService.hideContextView(); + })); + domNode.appendChild(closeBtn); + + const question = $('p.question', {}, nls.localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate VS Code?")); + domNode.appendChild(question); + + const yesBtn = $('div.button', {}, nls.localize('screenReaderDetectedExplanation.answerYes', "Yes")); + this.toDispose.push(addDisposableListener(yesBtn, 'click', () => { + this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { + key: 'editor.accessibilitySupport', + value: 'on' + }); + this.contextViewService.hideContextView(); + })); + domNode.appendChild(yesBtn); + + const noBtn = $('div.button', {}, nls.localize('screenReaderDetectedExplanation.answerNo', "No")); + this.toDispose.push(addDisposableListener(noBtn, 'click', () => { + this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { + key: 'editor.accessibilitySupport', + value: 'off' + }); + this.contextViewService.hideContextView(); + })); + domNode.appendChild(noBtn); + + const clear = $('div'); + clear.style.clear = 'both'; + domNode.appendChild(clear); + + const br = $('br'); + domNode.appendChild(br); + + const hr = $('hr'); + domNode.appendChild(hr); + + const explanation1 = $('p.body1', {}, nls.localize('screenReaderDetectedExplanation.body1', "VS Code is now optimized for usage with a screen reader.")); + domNode.appendChild(explanation1); + + const explanation2 = $('p.body2', {}, nls.localize('screenReaderDetectedExplanation.body2', "Additionally, due to limitations of WAI-ARIA, we have disabled certain editor features which cannot be currently expressed to screen readers: e.g. word wrapping, folding, auto closing brackets, etc.")); + domNode.appendChild(explanation2); + + container.appendChild(domNode); + + this.toDispose.push(attachStylerCallback(this.themeService, { widgetShadow, editorWidgetBackground }, colors => { + domNode.style.backgroundColor = colors.editorWidgetBackground; + if (colors.widgetShadow) { + domNode.style.boxShadow = `0 2px 8px ${colors.widgetShadow}`; + } + })); + + return { + dispose: () => { this.dispose(); } + }; + } +} diff --git a/src/vs/workbench/browser/parts/editor/media/close-big-dark.svg b/src/vs/workbench/browser/parts/editor/media/close-big-dark.svg new file mode 100644 index 0000000000000000000000000000000000000000..ce0e58964059e2a0a64944f7bd561f649ca3a448 --- /dev/null +++ b/src/vs/workbench/browser/parts/editor/media/close-big-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/editor/media/close-big.svg b/src/vs/workbench/browser/parts/editor/media/close-big.svg new file mode 100644 index 0000000000000000000000000000000000000000..fde34404d4eb8537f8bf76eb9f76127872ef68ba --- /dev/null +++ b/src/vs/workbench/browser/parts/editor/media/close-big.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/editor/media/editorstatus.css b/src/vs/workbench/browser/parts/editor/media/editorstatus.css index b4fd4bdd7d21f31455db4e63adfa4066fd02066d..9029f71eac7e8f8eb3f0122769df51b15c12eca3 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorstatus.css +++ b/src/vs/workbench/browser/parts/editor/media/editorstatus.css @@ -18,7 +18,78 @@ padding: 0 5px 0 5px; } -.monaco-workbench .editor-statusbar-item > .editor-status-metadata, -.monaco-workbench > .part.statusbar > .statusbar-item > .editor-statusbar-item > a.editor-status-screenreadermode { +.monaco-workbench .editor-statusbar-item > .editor-status-metadata { cursor: default !important; } + + +.monaco-shell .screen-reader-detected-explanation { + width: 420px; + top: 30px; + right: 6px; + padding: 1em; + cursor: default; +} + +.monaco-shell .screen-reader-detected-explanation .cancel { + position: absolute; + top: 0; + right: 0; + margin: .5em 0 0; + padding: .5em; + width: 22px; + height: 22px; + border: none; + cursor: pointer; +} + +.monaco-shell .screen-reader-detected-explanation h2 { + margin: 0; + padding: 0; + font-weight: 400; + font-size: 1.8em; +} + +.monaco-shell .screen-reader-detected-explanation p { + font-size: 1.2em; +} + +.monaco-shell .screen-reader-detected-explanation p.question { + font-size: 1.4em; + font-weight: bold; +} + +.monaco-shell .screen-reader-detected-explanation .button { + color: white; + border: none; + cursor: pointer; + background-color: #007ACC; + padding-left: 12px; + padding-right: 12px; + border: 4px solid #007ACC; + border-radius: 4px; + float: left; + margin-right: 5px; +} + +.monaco-shell.vs .screen-reader-detected-explanation .cancel { + background: url('close-big.svg') center center no-repeat; +} +.monaco-shell.vs .screen-reader-detected-explanation .cancel:hover { + background-color: #eaeaea; +} + +.monaco-shell.vs-dark .screen-reader-detected-explanation .cancel, +.monaco-shell.hc-black .screen-reader-detected-explanation .cancel { + background: url('close-big-dark.svg') center center no-repeat; +} +.monaco-shell.vs-dark .screen-reader-detected-explanation .cancel:hover { + background-color: rgba(30,30,30,0.8); +} + +.monaco-shell.hc-black .screen-reader-detected-explanation .cancel { + opacity: 0.6; +} +.monaco-shell.hc-black .screen-reader-detected-explanation .cancel:hover { + opacity: 1; +}