提交 4bd6bdfe 编写于 作者: M Michel Kaporin

Updated API to declarative way for supplying color format.

上级 3713e4de
......@@ -9,7 +9,7 @@ import { MarkedString } from 'vs/base/common/htmlContent';
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { LanguageId, LanguageIdentifier, ColorMode } from 'vs/editor/common/modes';
import { LanguageId, LanguageIdentifier } from 'vs/editor/common/modes';
import { LineTokens } from 'vs/editor/common/core/lineTokens';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Position, IPosition } from 'vs/editor/common/core/position';
......@@ -62,9 +62,11 @@ export interface IModelDecorationOverviewRulerOptions {
position: OverviewRulerLane;
}
export type IColorFormat = string | { opaque: string, transparent: string };
export interface IColorInfo {
color: Color;
mode: ColorMode;
format: IColorFormat;
availableFormats: IColorFormat[];
}
/**
* Options for a model decoration.
......
......@@ -173,7 +173,8 @@ export interface MarkedStringHover {
// TODO@michel documentation
export interface ColorHover {
color: Color;
mode: ColorMode;
format: IColorFormat;
availableFormats: IColorFormat[];
/**
* The range to which this hover applies. When missing, the
* editor will use the range at the current position or the
......@@ -676,16 +677,13 @@ export interface LinkProvider {
/**
* A color inside the editor.
*/
export type IColorFormat = string | { opaque: string, transparent: string };
export interface IColorInfo {
color: Color;
mode: ColorMode;
format: IColorFormat;
availableFormats: IColorFormat[];
range: IRange;
}
export enum ColorMode {
RGBA = 0,
Hex = 1,
HSLA = 2
}
/**
* A provider of colors.
*/
......
......@@ -14,6 +14,7 @@ import { editorWidgetBackground, editorWidgetBorder } from "vs/platform/theme/co
import { ColorProviderRegistry, IColorInfo } from "vs/editor/common/modes";
import { TPromise } from "vs/base/common/winjs.base";
import { getColors } from "vs/editor/contrib/colorPicker/common/colorPicker";
import { IRange } from "vs/editor/common/core/range";
@editorContribution
export class ColorPicker implements IEditorContribution {
......@@ -83,8 +84,8 @@ export class ColorPicker implements IEditorContribution {
return;
}
this.computePromise = getColors(this.editor.getModel()).then(colors => {
this.updateDecorations(colors);
this.computePromise = getColors(this.editor.getModel()).then(colorInfos => {
this.updateDecorations(colorInfos);
this.computePromise = null;
});
}
......@@ -100,24 +101,30 @@ export class ColorPicker implements IEditorContribution {
}
}
private updateDecorations(colors: IColorInfo[]): void {
private updateDecorations(colorInfos: IColorInfo[]): void {
this.editor.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => {
let newDecorations: IModelDeltaDecoration[] = [];
for (let c of colors) {
newDecorations.push({
range: {
startLineNumber: c.range.startLineNumber,
startColumn: c.range.startColumn,
endLineNumber: c.range.endLineNumber,
endColumn: c.range.endColumn
},
for (let c of colorInfos) {
const range: IRange = {
startLineNumber: c.range.startLineNumber,
startColumn: c.range.startColumn,
endLineNumber: c.range.endLineNumber,
endColumn: c.range.endColumn
};
const decoration = {
range: range,
options: {
colorInfo: {
color: c.color,
mode: c.mode
format: c.format,
availableFormats: c.availableFormats
}
}
});
};
newDecorations.push(decoration);
}
this.currentDecorations = changeAccessor.deltaDecorations(this.currentDecorations, newDecorations);
......
......@@ -5,20 +5,20 @@
import { ColorPickerWidget } from "vs/editor/contrib/colorPicker/browser/colorPickerWidget";
import { Color, RGBA } from "vs/base/common/color";
import { ColorMode } from "vs/editor/common/modes";
import { ColorFormatter } from "vs/editor/contrib/colorPicker/common/colorFormatter";
export class ColorPickerModel {
public widget: ColorPickerWidget;
public saturationSelection: ISaturationState;
public originalColor: string;
public widget: ColorPickerWidget;
private _color: Color;
private _selectedColor: string;
private _opacity: number;
private _hue: Color;
private _colorModel: ColorMode;
private _formatter: ColorFormatter;
private _colorModelIndex: number;
constructor() {
......@@ -33,13 +33,7 @@ export class ColorPickerModel {
this._hue = color;
}
if (this._colorModel === ColorMode.RGBA) {
this.selectedColorString = color.toRGBA().toString();
} else if (this._colorModel === ColorMode.Hex) {
this.selectedColorString = color.toRGBHex();
} else {
this.selectedColorString = color.toHSLA().toString();
}
this.selectedColorString = this._formatter.toString(this._color);
}
public get color(): Color {
......@@ -74,10 +68,6 @@ export class ColorPickerModel {
public set opacity(opacity: number) {
this._opacity = opacity;
if (this._colorModel === ColorMode.Hex) {
this.colorModel = ColorMode.RGBA;
}
const rgba = this._color.toRGBA();
this.color = Color.fromRGBA(new RGBA(rgba.r, rgba.g, rgba.b, opacity * 255));
......@@ -90,33 +80,20 @@ export class ColorPickerModel {
return this._opacity;
}
public set colorModel(model: ColorMode) {
this._colorModel = model;
this._colorModelIndex = model;
public set formatter(formatter: ColorFormatter) {
this._formatter = formatter;
if (this._selectedColor) {
this.color = this._color; // Refresh selected colour string state
}
}
public get colorModel(): ColorMode {
return this._colorModel;
public get formatter(): ColorFormatter {
return this._formatter;
}
public nextColorModel() { // should go to the controller perhaps
this._colorModelIndex++;
if (this._colorModelIndex > 2) {
this._colorModelIndex = 0;
}
// Skip hex model if opacity is set
if (this._colorModelIndex === ColorMode.Hex && this._opacity !== 1) {
this.nextColorModel();
return;
}
this.colorModel = this._colorModelIndex;
throw new Error('not implemented');
}
}
......
......@@ -46,7 +46,7 @@ export class ColorPickerBody extends Disposable {
const g = c.g;
const b = c.b;
this.opacityOverlay.style.background = `linear-gradient(to bottom, rgba(${r}, ${g}, ${b}, 1) 0%, rgba(${r}, ${g}, ${b}, 0.83) 17%, rgba(${r}, ${g}, ${b}, 0.67) 33%, rgba(${r}, ${g}, ${b}, 0.5) 50%, rgba(${r}, ${g}, ${b}, 0.33) 67%, rgba(${r}, ${g}, ${b}, 0.17) 83%, rgba(${r}, ${g}, ${b}, 0) 100%)`;
this.opacityOverlay.style.background = `linear-gradient(to bottom, rgba(${r}, ${g}, ${b}, 1) 0%, rgba(${r}, ${g}, ${b}, 0) 100%)`;
}
private registerListeners(): void {
......@@ -105,7 +105,7 @@ export class ColorPickerBody extends Disposable {
}
const slider = element === this.hueStrip ? this.hueSlider : this.opacitySlider;
const strip = element === this.hueStrip ? this.hueStrip : this.opacityStrip;
const initialColorModel = this.model.colorModel;
const initialColorModel = this.model.formatter;
// Update slider position if clicked on a strip itself
if (e.target === this.hueStrip || e.target === this.opacityStrip) {
......@@ -143,8 +143,8 @@ export class ColorPickerBody extends Disposable {
updateModel();
// Change back from RGBA to HEX if opacity touched
if (this.model.colorModel !== initialColorModel && this.model.opacity === 1) {
this.model.colorModel = initialColorModel;
if (this.model.formatter !== initialColorModel && this.model.opacity === 1) {
this.model.formatter = initialColorModel;
}
}, () => {
strip.style.cursor = '-webkit-grab';
......
......@@ -33,12 +33,12 @@ export class ColorPickerHeader extends Disposable {
public updatePickedColor() {
this.pickedColorNode.textContent = this.model.selectedColorString;
this.pickedColorNode.style.backgroundColor = this.model.selectedColorString;
this.pickedColorNode.style.backgroundColor = this.model.color.toString();
}
private drawPickedColorBox() {
this.pickedColorNode = $('.picked-color');
this.pickedColorNode.style.backgroundColor = this.model.selectedColorString;
this.pickedColorNode.style.backgroundColor = this.model.color.toString();
this.pickedColorNode.textContent = this.model.selectedColorString;
dom.append(this.domNode, this.pickedColorNode);
}
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Color } from "vs/base/common/color";
interface Node {
(color: Color): string;
}
function createLiteralNode(value: string): Node {
return () => value;
}
const RGBA_ENDRANGE = 255;
const HSL_HUERANGE = 360;
const HSL_ENDRANGE = 1;
function normalize(value: number, start?: number, end?: number, currentEndRange?: number): number {
let val = value;
if (start || end) {
// More safety checks
// if (!start) {
// throw new Error('Color format range defined is not correct. There is no range start.');
// }
// if (!end) {
// throw new Error('Color format range defined is not correct. There is no range end.');
// }
if (start > end) {
throw new Error('Color format range defined is not correct. Range start is bigger than end.');
}
if (start === end) {
throw new Error('Color format range defined is not correct. Range start is the same as end.');
}
const ratio = val / currentEndRange;
val = ratio * (end - start) + start;
}
return val;
};
function createPropertyNode(variable: string, fractionDigits: number, type: string, min: number, max: number): Node {
return color => {
let absoluteValue: number;
switch (variable) {
case 'red':
absoluteValue = normalize(color.toRGBA().r, min, max, RGBA_ENDRANGE);
break;
case 'blue':
absoluteValue = normalize(color.toRGBA().g, min, max, RGBA_ENDRANGE);
break;
case 'green':
absoluteValue = normalize(color.toRGBA().b, min, max, RGBA_ENDRANGE);
break;
case 'alpha':
absoluteValue = normalize(color.toRGBA().a, min, max, RGBA_ENDRANGE);
break;
case 'hue':
absoluteValue = normalize(color.toHSLA().h, min, max, HSL_HUERANGE);
break;
case 'saturation':
absoluteValue = normalize(color.toHSLA().s, min, max, HSL_ENDRANGE);
break;
case 'luminosity':
absoluteValue = normalize(color.toHSLA().l, min, max, HSL_ENDRANGE);
break;
}
if (absoluteValue === undefined) {
throw new Error(`${variable} is not supported as a color format.`);
}
let value: number | string;
if (type === 'f') {
fractionDigits = fractionDigits ? fractionDigits : 2; // 2 is default
value = absoluteValue.toFixed(fractionDigits);
} else if (type === 'x' || type === 'X') {
value = normalize(absoluteValue, min, max, RGBA_ENDRANGE).toString(16);
if (value.length !== 2) {
value = '0' + value;
}
if (type === 'X') {
value = value.toUpperCase();
}
} else { // also 'd'-case
value = absoluteValue.toFixed(0);
}
return value.toString();
};
}
export class ColorFormatter {
/*
Variables
- red
- green
- blue
- hue
- saturation
- luminosity
- alpha
Number formats
- decimal - d
- float - f
- hex - x X
Number ranges
- 0-1
- 0-255
- 0-100
- arbitrary
Examples
"{red}" - 123
"{red:d}" - 123
"{red:x}" - af
"{red:X}" - AF
"{red:d[0-255]}" - AF
"{red:x[0-255]}" - AF
"{red:X[0-1024]}" - FEFE
"{red:2f}" - 123.51
"{red:1f}" - 123.5
"{red:2f[0-1]}" - 1.23
- default format: decimal
- default range: 0-255
*/
private tree: Node[] = [];
// Group 0: variable
// Group 1: decimal digits
// Group 2: floating/integer/hex
// Group 3: range begin
// Group 4: range end
private static PATTERN = /{(\w+)(?::(\d*)(\w)+(?:\[(\d+)-(\d+)\])?)?}/g;
constructor(format: string) {
this.parse(format);
// this.tree.push(createLiteralNode('new Color('));
// this.tree.push(createPropertyNode('red', 'd', 0, 255));
// this.tree.push(createLiteralNode(')'));
}
private parse(format: string): void {
let match = ColorFormatter.PATTERN.exec(format);
let startIndex = 0;
// if no match -> erroor throw new Error(`${format} is not consistent with color format syntax.`);
while (match !== null) {
const index = match.index;
if (startIndex < index) {
this.tree.push(createLiteralNode(format.substring(startIndex, index)));
}
// add more parser catches
const variable = match[1];
if (!variable) {
throw new Error(`${variable} is not defined`);
}
const decimals = parseInt(match[2]);
const type = match[3];
const startRange = parseInt(match[4]);
const endRange = parseInt(match[5]);
this.tree.push(createPropertyNode(variable, decimals, type, startRange, endRange));
startIndex = index + match[0].length;
match = ColorFormatter.PATTERN.exec(format);
}
this.tree.push(createLiteralNode(format.substring(startIndex, format.length)));
}
public toString(color: Color): string {
let colorString = [];
this.tree.forEach(node => {
colorString.push(node(color));
});
return colorString.join('');
}
}
......@@ -4,28 +4,24 @@
*--------------------------------------------------------------------------------------------*/
import { TPromise } from "vs/base/common/winjs.base";
import { ColorProviderRegistry, IColorInfo, ColorProvider } from "vs/editor/common/modes";
import { ColorProviderRegistry, IColorInfo } from "vs/editor/common/modes";
import { asWinJsPromise } from "vs/base/common/async";
import { onUnexpectedExternalError } from "vs/base/common/errors";
import { IReadOnlyModel } from "vs/editor/common/editorCommon";
export function getColors(model: IReadOnlyModel): TPromise<IColorInfo[]> {
let colors: IColorInfo[] = [];
let colorInfo: IColorInfo[] = [];
// ask all providers for colors in parallel
const promises = ColorProviderRegistry.ordered(model).reverse().map(provider => {
return asWinJsPromise(token => provider.provideColors(model, token)).then(result => {
if (Array.isArray(result)) {
colors = colors.concat(result);
colorInfo = colorInfo.concat(result);
}
}, onUnexpectedExternalError);
});
return TPromise.join(promises).then(() => {
return colors;
return colorInfo;
});
}
export function changeMode(provider: ColorProvider, colorInfo: IColorInfo): TPromise<string> {
throw new Error('not implemented');
}
\ No newline at end of file
......@@ -25,6 +25,7 @@ import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDeco
import { ColorPickerModel } from "vs/editor/contrib/colorPicker/browser/colorPickerModel";
import { ColorPickerWidget } from "vs/editor/contrib/colorPicker/browser/colorPickerWidget";
import { IColorInfo } from 'vs/editor/common/editorCommon';
import { ColorFormatter } from "vs/editor/contrib/colorPicker/common/colorFormatter";
class ModesContentComputer implements IHoverComputer<Hover[]> {
......@@ -98,8 +99,9 @@ class ModesContentComputer implements IHoverComputer<Hover[]> {
if (colorInfo) {
return {
color: colorInfo.color,
mode: colorInfo.mode,
range: range
format: colorInfo.format,
availableFormats: colorInfo.availableFormats,
range: range,
};
}
......@@ -294,11 +296,16 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
const widget = this._register(new ColorPickerWidget(model, this._editor));
model.widget = widget;
model.originalColor = msg.color.toRGBA().toString();
model.colorModel = msg.mode;
if (typeof msg.format !== 'string') { // TODO: take into account opaque and transparent
msg.format = msg.format.opaque;
}
model.formatter = new ColorFormatter(msg.format);
model.color = msg.color;
this._colorPicker = widget;
fragment.appendChild(widget.getDomNode());
console.log(this._editor.getModel());
}
});
......
......@@ -265,12 +265,7 @@ declare module 'vscode' {
static fromHex(hex: string): Color;
}
export enum ColorMode {
RGBA = 0,
Hex = 1, // should we account for 4-byte hex?
HSLA = 2
}
export type IColorFormat = string | { opaque: string, transparent: string };
// TODO@Michel
export class ColorInfo {
/**
......@@ -280,14 +275,15 @@ declare module 'vscode' {
color: Color;
mode: ColorMode;
format: IColorFormat;
availableFormats: IColorFormat[];
constructor(range: Range, color: Color, mode: ColorMode);
constructor(range: Range, color: Color, format: IColorFormat, availableFormats: IColorFormat[]);
}
export interface DocumentColorProvider {
provideDocumentColors(document: TextDocument, token: CancellationToken): ProviderResult<ColorInfo[]>;
provideColorFormat(colorInfo: ColorInfo): ProviderResult<string>;
}
export namespace languages {
......
......@@ -269,15 +269,37 @@ export class MainThreadLanguageFeatures extends MainThreadLanguageFeaturesShape
// --- colors
$registerDocumentColorProvider(handle: number, selector: vscode.DocumentSelector): TPromise<any> {
const proxy = this._proxy;
this._registrations[handle] = modes.ColorProviderRegistry.register(selector, <modes.ColorProvider>{
provideColors: (model, token) => {
return wireCancellationToken(token, this._proxy.$provideDocumentColors(handle, model.uri))
provideColors: function (model, token) {
const provider = this;
return wireCancellationToken(token, proxy.$provideDocumentColors(handle, model.uri))
.then((colorInfos) => {
return colorInfos.map(c => {
let format: modes.IColorFormat;
if (typeof c.format === 'string') {
format = c.format;
} else {
format = { opaque: c.format[0], transparent: c.format[1] };
}
let availableFormats: modes.IColorFormat[] = [];
c.availableFormats.forEach(format => {
if (typeof format === 'string') {
availableFormats.push(format);
} else {
availableFormats.push({
opaque: format[0],
transparent: format[1]
});
}
});
return {
color: Color.fromRGBA(new RGBA(c.color[0], c.color[1], c.color[2], c.color[3] * 255)),
mode: c.mode,
range: c.range
format: format,
availableFormats: availableFormats,
range: c.range,
provider: provider
};
});
});
......
......@@ -492,7 +492,6 @@ export function createApiFactory(
CodeLens: extHostTypes.CodeLens,
Color: extHostTypes.Color,
ColorInfo: extHostTypes.ColorInfo,
ColorMode: extHostTypes.ColorMode,
EndOfLine: extHostTypes.EndOfLine,
CompletionItem: extHostTypes.CompletionItem,
CompletionItemKind: extHostTypes.CompletionItemKind,
......
......@@ -35,7 +35,7 @@ import { IConfigurationData } from 'vs/platform/configuration/common/configurati
import { IPickOpenEntry, IPickOptions } from 'vs/platform/quickOpen/common/quickOpen';
import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions';
import { EndOfLine, TextEditorLineNumbersStyle, ColorMode } from 'vs/workbench/api/node/extHostTypes';
import { EndOfLine, TextEditorLineNumbersStyle } from 'vs/workbench/api/node/extHostTypes';
import { TaskSet } from 'vs/workbench/parts/tasks/common/tasks';
......@@ -453,10 +453,10 @@ export namespace ObjectIdentifier {
export abstract class ExtHostHeapServiceShape {
$onGarbageCollection(ids: number[]): void { throw ni(); }
}
export interface IColorInfo {
color: [number, number, number, number | undefined];
mode: ColorMode;
format: string | [string, string];
availableFormats: (string | [string, string])[];
range: IRange;
}
......
......@@ -372,9 +372,26 @@ export namespace DocumentLink {
export namespace DocumentColor {
export function from(colorInfo: vscode.ColorInfo): IColorInfo {
let format: string | [string, string];
if (typeof colorInfo.format === 'string') {
format = colorInfo.format;
} else {
format = [colorInfo.format.opaque, colorInfo.format.transparent];
}
let availableFormats: (string | [string, string])[] = [];
colorInfo.availableFormats.forEach(format => {
if (typeof format === 'string') {
availableFormats.push(format);
} else {
availableFormats.push([format.opaque, format.transparent]);
}
});
return {
color: [colorInfo.color.red, colorInfo.color.green, colorInfo.color.blue, colorInfo.color.alpha],
mode: <number>colorInfo.mode,
format: format,
availableFormats: availableFormats,
range: fromRange(colorInfo.range)
};
}
......
......@@ -1043,32 +1043,34 @@ export class Color {
}
}
export enum ColorMode {
RGBA = 0,
Hex = 1,
HSLA = 2
}
export type IColorFormat = string | { opaque: string, transparent: string };
export class ColorInfo {
range: Range;
color: Color;
mode: ColorMode;
format: IColorFormat;
availableFormats: IColorFormat[];
constructor(range: Range, color: Color, mode: ColorMode) {
constructor(range: Range, color: Color, format: IColorFormat, availableFormats: IColorFormat[]) {
if (color && !(color instanceof Color)) {
throw illegalArgument('color');
}
if (mode && !(mode in ColorMode)) {
throw illegalArgument('mode');
if (format && (typeof format !== 'string') && !format.opaque && !format.transparent && typeof format.opaque !== 'string' && typeof format.transparent !== 'string') {
throw illegalArgument('format');
}
if (availableFormats && !Array.isArray(availableFormats)) {
throw illegalArgument('availableFormats');
}
if (!Range.isRange(range) || range.isEmpty) {
throw illegalArgument('range');
}
this.range = range;
this.color = color;
this.mode = mode;
this.format = format;
this.availableFormats = availableFormats;
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册