themeService.ts 16.6 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

B
Benjamin Pasero 已提交
7
import {TPromise} from 'vs/base/common/winjs.base';
E
Erich Gamma 已提交
8 9
import nls = require('vs/nls');
import Paths = require('vs/base/common/paths');
10
import Json = require('vs/base/common/json');
E
Erich Gamma 已提交
11
import {IThemeExtensionPoint} from 'vs/platform/theme/common/themeExtensionPoint';
A
Alex Dima 已提交
12
import {IExtensionService} from 'vs/platform/extensions/common/extensions';
13
import {ExtensionsRegistry, IExtensionMessageCollector} from 'vs/platform/extensions/common/extensionsRegistry';
M
Martin Aeschlimann 已提交
14 15 16 17
import {IThemeService, IThemeData} from 'vs/workbench/services/themes/common/themeService';
import {getBaseThemeId, getSyntaxThemeId} from 'vs/platform/theme/common/themes';
import {IWindowService} from 'vs/workbench/services/window/electron-browser/windowService';
import {IStorageService, StorageScope} from 'vs/platform/storage/common/storage';
18
import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry';
M
Martin Aeschlimann 已提交
19 20
import {$} from 'vs/base/browser/builder';
import Event, {Emitter} from 'vs/base/common/event';
E
Erich Gamma 已提交
21 22 23 24 25 26

import plist = require('vs/base/node/plist');
import pfs = require('vs/base/node/pfs');

// implementation

M
Martin Aeschlimann 已提交
27 28 29
const DEFAULT_THEME_ID = 'vs-dark vscode-theme-defaults-themes-dark_plus-json';

const THEME_CHANNEL = 'vscode:changeTheme';
B
Benjamin Pasero 已提交
30
const THEME_PREF = 'workbench.theme';
M
Martin Aeschlimann 已提交
31 32

let defaultBaseTheme = getBaseThemeId(DEFAULT_THEME_ID);
E
Erich Gamma 已提交
33

34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
const defaultThemeExtensionId = 'vscode-theme-defaults';
const oldDefaultThemeExtensionId = 'vscode-theme-colorful-defaults';

function validateThemeId(theme: string) : string {
	// migrations
	switch (theme) {
		case 'vs': return `vs ${defaultThemeExtensionId}-themes-light_vs-json`;
		case 'vs-dark': return `vs-dark ${defaultThemeExtensionId}-themes-dark_vs-json`;
		case 'hc-black': return `hc-black ${defaultThemeExtensionId}-themes-hc_black-json`;
		case `vs ${oldDefaultThemeExtensionId}-themes-light_plus-tmTheme`: return `vs ${defaultThemeExtensionId}-themes-light_plus-json`;
		case `vs-dark ${oldDefaultThemeExtensionId}-themes-dark_plus-tmTheme`: return `vs-dark ${defaultThemeExtensionId}-themes-dark_plus-json`;
	}
	return theme;
}

49
let themesExtPoint = ExtensionsRegistry.registerExtensionPoint<IThemeExtensionPoint[]>('themes', {
E
Erich Gamma 已提交
50 51
	description: nls.localize('vscode.extension.contributes.themes', 'Contributes textmate color themes.'),
	type: 'array',
52
	defaultSnippets: [{ body: [{ label: '{{label}}', uiTheme: 'vs-dark', path: './themes/{{id}}.tmTheme.' }] }],
E
Erich Gamma 已提交
53 54
	items: {
		type: 'object',
55
		defaultSnippets: [{ body: { label: '{{label}}', uiTheme: 'vs-dark', path: './themes/{{id}}.tmTheme.' } }],
E
Erich Gamma 已提交
56 57 58 59 60 61 62
		properties: {
			label: {
				description: nls.localize('vscode.extension.contributes.themes.label', 'Label of the color theme as shown in the UI.'),
				type: 'string'
			},
			uiTheme: {
				description: nls.localize('vscode.extension.contributes.themes.uiTheme', 'Base theme defining the colors around the editor: \'vs\' is the light color theme, \'vs-dark\' is the dark color theme.'),
63
				enum: ['vs', 'vs-dark', 'hc-black']
E
Erich Gamma 已提交
64 65 66 67 68 69 70 71 72
			},
			path: {
				description: nls.localize('vscode.extension.contributes.themes.path', 'Path of the tmTheme file. The path is relative to the extension folder and is typically \'./themes/themeFile.tmTheme\'.'),
				type: 'string'
			}
		}
	}
});

73 74 75 76 77 78 79 80 81 82 83
interface ThemeSettingStyle {
	background?: string;
	foreground?: string;
	fontStyle?: string;
	caret?: string;
	invisibles?: string;
	lineHighlight?: string;
	selection?: string;
}

interface ThemeSetting {
84 85
	name?: string;
	scope?: string | string[];
86 87 88 89 90 91 92 93 94
	settings: ThemeSettingStyle[];
}

interface ThemeDocument {
	name: string;
	include: string;
	settings: ThemeSetting[];
}

95 96 97 98 99
interface IInternalThemeData extends IThemeData {
	styleSheetContent?: string;
	extensionId: string;
}

E
Erich Gamma 已提交
100 101 102
export class ThemeService implements IThemeService {
	serviceId = IThemeService;

103
	private knownThemes: IInternalThemeData[];
M
Martin Aeschlimann 已提交
104 105 106
	private currentTheme: string;
	private container: HTMLElement;
	private onThemeChange: Emitter<string>;
E
Erich Gamma 已提交
107

M
Martin Aeschlimann 已提交
108
	constructor(
109
			@IExtensionService private extensionService: IExtensionService,
M
Martin Aeschlimann 已提交
110
			@IWindowService private windowService: IWindowService,
111 112 113
			@IStorageService private storageService: IStorageService,
			@ITelemetryService private telemetryService: ITelemetryService) {

E
Erich Gamma 已提交
114
		this.knownThemes = [];
M
Martin Aeschlimann 已提交
115
		this.onThemeChange = new Emitter<string>();
E
Erich Gamma 已提交
116 117 118 119 120 121

		themesExtPoint.setHandler((extensions) => {
			for (let ext of extensions) {
				this.onThemes(ext.description.extensionFolderPath, ext.description.id, ext.value, ext.collector);
			}
		});
M
Martin Aeschlimann 已提交
122 123 124 125 126 127 128 129 130 131

		windowService.onBroadcast(e => {
			if (e.channel === THEME_CHANNEL && typeof e.payload === 'string') {
				this.setTheme(e.payload, false);
			}
		});
	}

	public get onDidThemeChange(): Event<string> {
		return this.onThemeChange.event;
E
Erich Gamma 已提交
132 133
	}

M
Martin Aeschlimann 已提交
134 135 136
	public initialize(container: HTMLElement): TPromise<boolean> {
		this.container = container;

B
Benjamin Pasero 已提交
137
		let themeId = this.storageService.get(THEME_PREF, StorageScope.GLOBAL, null);
M
Martin Aeschlimann 已提交
138 139
		if (!themeId) {
			themeId = DEFAULT_THEME_ID;
B
Benjamin Pasero 已提交
140
			this.storageService.store(THEME_PREF, themeId, StorageScope.GLOBAL);
M
Martin Aeschlimann 已提交
141 142 143 144 145 146 147 148 149 150 151 152
		}
		return this.setTheme(themeId, false);
	}

	public setTheme(themeId: string, broadcastToAllWindows: boolean) : TPromise<boolean> {
		if (!themeId) {
			return TPromise.as(false);
		}
		if (themeId === this.currentTheme) {
			if (broadcastToAllWindows) {
				this.windowService.broadcast({ channel: THEME_CHANNEL, payload: themeId });
			}
153
			return TPromise.as(true);
M
Martin Aeschlimann 已提交
154 155 156 157
		}

		themeId = validateThemeId(themeId); // migrate theme ids

158 159
		let onApply = (newTheme: IInternalThemeData) => {
			let newThemeId = newTheme.id;
M
Martin Aeschlimann 已提交
160 161 162 163 164 165 166 167
			if (this.container) {
				if (this.currentTheme) {
					$(this.container).removeClass(this.currentTheme);
				}
				this.currentTheme = newThemeId;
				$(this.container).addClass(newThemeId);
			}

B
Benjamin Pasero 已提交
168
			this.storageService.store(THEME_PREF, newThemeId, StorageScope.GLOBAL);
M
Martin Aeschlimann 已提交
169 170
			if (broadcastToAllWindows) {
				this.windowService.broadcast({ channel: THEME_CHANNEL, payload: newThemeId });
171 172
			} else {
				this.sendTelemetry(newTheme);
M
Martin Aeschlimann 已提交
173 174 175 176 177 178 179 180
			}
			this.onThemeChange.fire(newThemeId);
		};

		return this.applyThemeCSS(themeId, DEFAULT_THEME_ID, onApply);
	}

	public getTheme() {
B
Benjamin Pasero 已提交
181
		return this.currentTheme || this.storageService.get(THEME_PREF, StorageScope.GLOBAL, DEFAULT_THEME_ID);
M
Martin Aeschlimann 已提交
182 183
	}

184
	private loadTheme(themeId: string, defaultId?: string): TPromise<IInternalThemeData> {
E
Erich Gamma 已提交
185
		return this.getThemes().then(allThemes => {
B
Benjamin Pasero 已提交
186
			let themes = allThemes.filter(t => t.id === themeId);
E
Erich Gamma 已提交
187
			if (themes.length > 0) {
188
				return <IInternalThemeData> themes[0];
E
Erich Gamma 已提交
189
			}
M
Martin Aeschlimann 已提交
190 191 192
			if (defaultId) {
				let themes = allThemes.filter(t => t.id === defaultId);
				if (themes.length > 0) {
193
					return <IInternalThemeData> themes[0];
M
Martin Aeschlimann 已提交
194 195
				}
			}
E
Erich Gamma 已提交
196 197 198 199
			return null;
		});
	}

200
	private applyThemeCSS(themeId: string, defaultId: string, onApply: (theme:IInternalThemeData) => void): TPromise<boolean> {
M
Martin Aeschlimann 已提交
201
		return this.loadTheme(themeId, defaultId).then(theme => {
E
Erich Gamma 已提交
202
			if (theme) {
M
Martin Aeschlimann 已提交
203
				return applyTheme(theme, onApply);
E
Erich Gamma 已提交
204
			}
205
			return false;
B
Benjamin Pasero 已提交
206
		});
E
Erich Gamma 已提交
207 208
	}

209
	public getThemes(): TPromise<IThemeData[]> {
A
Alex Dima 已提交
210
		return this.extensionService.onReady().then(isReady => {
E
Erich Gamma 已提交
211 212 213 214
			return this.knownThemes;
		});
	}

215
	private onThemes(extensionFolderPath: string, extensionId: string, themes: IThemeExtensionPoint[], collector: IExtensionMessageCollector): void {
E
Erich Gamma 已提交
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
		if (!Array.isArray(themes)) {
			collector.error(nls.localize(
				'reqarray',
				"Extension point `{0}` must be an array.",
				themesExtPoint.name
			));
			return;
		}
		themes.forEach(theme => {
			if (!theme.path || (typeof theme.path !== 'string')) {
				collector.error(nls.localize(
					'reqpath',
					"Expected string in `contributes.{0}.path`. Provided value: {1}",
					themesExtPoint.name,
					String(theme.path)
				));
				return;
			}
			let normalizedAbsolutePath = Paths.normalize(Paths.join(extensionFolderPath, theme.path));

			if (normalizedAbsolutePath.indexOf(extensionFolderPath) !== 0) {
				collector.warn(nls.localize('invalid.path.1', "Expected `contributes.{0}.path` ({1}) to be included inside extension's folder ({2}). This might make the extension non-portable.", themesExtPoint.name, normalizedAbsolutePath, extensionFolderPath));
			}

Y
Yuki Ueda 已提交
240
			let themeSelector = toCSSSelector(extensionId + '-' + Paths.normalize(theme.path));
E
Erich Gamma 已提交
241 242 243 244
			this.knownThemes.push({
				id: `${theme.uiTheme || defaultBaseTheme} ${themeSelector}`,
				label: theme.label || Paths.basename(theme.path),
				description: theme.description,
245 246
				path: normalizedAbsolutePath,
				extensionId: extensionId
E
Erich Gamma 已提交
247
			});
B
Benjamin Pasero 已提交
248
		});
E
Erich Gamma 已提交
249
	}
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266

	private themeExtensionsActivated = {};
	private sendTelemetry(themeData: IInternalThemeData) {
		if (!this.themeExtensionsActivated[themeData.extensionId]) {
			let description = ExtensionsRegistry.getExtensionDescription(themeData.extensionId);
			if (description) {
				this.telemetryService.publicLog('activatePlugin', {
					id: description.id,
					name: description.name,
					isBuiltin: description.isBuiltin,
					publisherDisplayName: description.publisher,
					themeId: themeData.id
				});
				this.themeExtensionsActivated[themeData.extensionId] = true;
			}
		}
	}
E
Erich Gamma 已提交
267 268
}

269

270

Y
Yuki Ueda 已提交
271
function toCSSSelector(str: string) {
272 273 274 275 276
	str = str.replace(/[^_\-a-zA-Z0-9]/g, '-');
	if (str.charAt(0).match(/[0-9\-]/)) {
		str = '_' + str;
	}
	return str;
E
Erich Gamma 已提交
277 278
}

279
function applyTheme(theme: IInternalThemeData, onApply: (theme:IInternalThemeData) => void): TPromise<boolean> {
E
Erich Gamma 已提交
280
	if (theme.styleSheetContent) {
B
Benjamin Pasero 已提交
281
		_applyRules(theme.styleSheetContent);
282
		onApply(theme);
M
Martin Aeschlimann 已提交
283
		return TPromise.as(true);
E
Erich Gamma 已提交
284
	}
285 286 287 288
	return _loadThemeDocument(theme.path).then(themeDocument => {
		let styleSheetContent = _processThemeObject(theme.id, themeDocument);
		theme.styleSheetContent = styleSheetContent;
		_applyRules(styleSheetContent);
289
		onApply(theme);
290
		return true;
291 292 293 294
	}, error => {
		return TPromise.wrapError(nls.localize('error.cannotloadtheme', "Unable to load {0}", theme.path));
	});
}
E
Erich Gamma 已提交
295

296 297 298
function _loadThemeDocument(themePath: string) : TPromise<ThemeDocument> {
	return pfs.readFile(themePath).then(content => {
		if (Paths.extname(themePath) === '.json') {
299
			let errors: Json.ParseError[] = [];
300
			let contentValue = <ThemeDocument> Json.parse(content.toString(), errors);
301
			if (errors.length > 0) {
302
				return TPromise.wrapError(new Error(nls.localize('error.cannotparsejson', "Problems parsing JSON theme file: {0}", errors.map(e => Json.getParseErrorMessage(e.error)).join(', '))));
303 304 305 306 307 308
			}
			if (contentValue.include) {
				return _loadThemeDocument(Paths.join(Paths.dirname(themePath), contentValue.include)).then(includedValue => {
					contentValue.settings = includedValue.settings.concat(contentValue.settings);
					return TPromise.as(contentValue);
				});
309
			}
310
			return TPromise.as(contentValue);
311 312 313 314 315
		} else {
			let parseResult = plist.parse(content.toString());
			if (parseResult.errors && parseResult.errors.length) {
				return TPromise.wrapError(new Error(nls.localize('error.cannotparse', "Problems parsing plist file: {0}", parseResult.errors.join(', '))));
			}
316
			return TPromise.as(parseResult.value);
E
Erich Gamma 已提交
317 318 319 320
		}
	});
}

321
function _processThemeObject(themeId: string, themeDocument: ThemeDocument): string {
B
Benjamin Pasero 已提交
322
	let cssRules: string[] = [];
E
Erich Gamma 已提交
323

324 325
	let themeSettings : ThemeSetting[] = themeDocument.settings;
	let editorSettings : ThemeSettingStyle = {
E
Erich Gamma 已提交
326 327 328 329 330 331 332 333
		background: void 0,
		foreground: void 0,
		caret: void 0,
		invisibles: void 0,
		lineHighlight: void 0,
		selection: void 0
	};

M
Martin Aeschlimann 已提交
334
	let themeSelector = `${getBaseThemeId(themeId)}.${getSyntaxThemeId(themeId)}`;
E
Erich Gamma 已提交
335 336

	if (Array.isArray(themeSettings)) {
337
		themeSettings.forEach((s : ThemeSetting, index, arr) => {
E
Erich Gamma 已提交
338 339 340
			if (index === 0 && !s.scope) {
				editorSettings = s.settings;
			} else {
341
				let scope: string | string[] = s.scope;
342
				let settings = s.settings;
E
Erich Gamma 已提交
343
				if (scope && settings) {
344
					let rules = Array.isArray(scope) ? <string[]> scope : scope.split(',');
B
Benjamin Pasero 已提交
345
					let statements = _settingsToStatements(settings);
E
Erich Gamma 已提交
346 347 348 349 350 351 352 353 354 355 356
					rules.forEach(rule => {
						rule = rule.trim().replace(/ /g, '.'); // until we have scope hierarchy in the editor dom: replace spaces with .

						cssRules.push(`.monaco-editor.${themeSelector} .token.${rule} { ${statements} }`);
					});
				}
			}
		});
	}

	if (editorSettings.background) {
B
Benjamin Pasero 已提交
357
		let background = new Color(editorSettings.background);
E
Erich Gamma 已提交
358 359 360
		//cssRules.push(`.monaco-editor.${themeSelector} { background-color: ${background}; }`);
		cssRules.push(`.monaco-editor.${themeSelector} .monaco-editor-background { background-color: ${background}; }`);
		cssRules.push(`.monaco-editor.${themeSelector} .glyph-margin { background-color: ${background}; }`);
361
		cssRules.push(`.${themeSelector} .monaco-workbench .monaco-editor-background { background-color: ${background}; }`);
E
Erich Gamma 已提交
362 363
	}
	if (editorSettings.foreground) {
B
Benjamin Pasero 已提交
364
		let foreground = new Color(editorSettings.foreground);
E
Erich Gamma 已提交
365 366 367 368
		cssRules.push(`.monaco-editor.${themeSelector} { color: ${foreground}; }`);
		cssRules.push(`.monaco-editor.${themeSelector} .token { color: ${foreground}; }`);
	}
	if (editorSettings.selection) {
B
Benjamin Pasero 已提交
369
		let selection = new Color(editorSettings.selection);
E
Erich Gamma 已提交
370 371 372 373
		cssRules.push(`.monaco-editor.${themeSelector} .focused .selected-text { background-color: ${selection}; }`);
		cssRules.push(`.monaco-editor.${themeSelector} .selected-text { background-color: ${selection.transparent(0.5)}; }`);
	}
	if (editorSettings.lineHighlight) {
B
Benjamin Pasero 已提交
374
		let lineHighlight = new Color(editorSettings.lineHighlight);
375
		cssRules.push(`.monaco-editor.${themeSelector}.focused .current-line { background-color: ${lineHighlight}; border:0; }`);
E
Erich Gamma 已提交
376 377
	}
	if (editorSettings.caret) {
B
Benjamin Pasero 已提交
378
		let caret = new Color(editorSettings.caret);
379 380
		let oppositeCaret = caret.opposite();
		cssRules.push(`.monaco-editor.${themeSelector} .cursor { background-color: ${caret}; border-color: ${caret}; color: ${oppositeCaret}; }`);
E
Erich Gamma 已提交
381 382
	}
	if (editorSettings.invisibles) {
B
Benjamin Pasero 已提交
383
		let invisibles = new Color(editorSettings.invisibles);
E
Erich Gamma 已提交
384
		cssRules.push(`.monaco-editor.${themeSelector} .token.whitespace { color: ${invisibles} !important; }`);
385
		cssRules.push(`.monaco-editor.${themeSelector} .lines-content .cigr { background: ${invisibles}; }`);
E
Erich Gamma 已提交
386 387 388 389 390
	}

	return cssRules.join('\n');
}

391
function _settingsToStatements(settings: ThemeSettingStyle): string {
B
Benjamin Pasero 已提交
392
	let statements: string[] = [];
E
Erich Gamma 已提交
393

B
Benjamin Pasero 已提交
394
	for (let settingName in settings) {
E
Erich Gamma 已提交
395 396 397
		var value = settings[settingName];
		switch (settingName) {
			case 'foreground':
B
Benjamin Pasero 已提交
398
				let foreground = new Color(value);
E
Erich Gamma 已提交
399 400 401 402
				statements.push(`color: ${foreground};`);
				break;
			case 'background':
				// do not support background color for now, see bug 18924
B
Benjamin Pasero 已提交
403
				//let background = new Color(value);
E
Erich Gamma 已提交
404 405 406
				//statements.push(`background-color: ${background};`);
				break;
			case 'fontStyle':
B
Benjamin Pasero 已提交
407
				let segments = value.split(' ');
E
Erich Gamma 已提交
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425
				segments.forEach(s => {
					switch (value) {
						case 'italic':
							statements.push(`font-style: italic;`);
							break;
						case 'bold':
							statements.push(`font-weight: bold;`);
							break;
						case 'underline':
							statements.push(`text-decoration: underline;`);
							break;
					}
				});
		}
	}
	return statements.join(' ');
}

B
Benjamin Pasero 已提交
426
let className = 'contributedColorTheme';
E
Erich Gamma 已提交
427 428

function _applyRules(styleSheetContent: string) {
B
Benjamin Pasero 已提交
429
	let themeStyles = document.head.getElementsByClassName(className);
E
Erich Gamma 已提交
430
	if (themeStyles.length === 0) {
B
Benjamin Pasero 已提交
431 432
		let elStyle = document.createElement('style');
		elStyle.type = 'text/css';
E
Erich Gamma 已提交
433 434 435 436
		elStyle.className = className;
		elStyle.innerHTML = styleSheetContent;
		document.head.appendChild(elStyle);
	} else {
B
Benjamin Pasero 已提交
437
		(<HTMLStyleElement>themeStyles[0]).innerHTML = styleSheetContent;
E
Erich Gamma 已提交
438 439 440
	}
}

B
Benjamin Pasero 已提交
441
interface RGBA { r: number; g: number; b: number; a: number; }
E
Erich Gamma 已提交
442 443 444

class Color {

B
Benjamin Pasero 已提交
445
	private parsed: RGBA;
E
Erich Gamma 已提交
446 447
	private str: string;

B
Benjamin Pasero 已提交
448
	constructor(arg: string | RGBA) {
E
Erich Gamma 已提交
449
		if (typeof arg === 'string') {
B
Benjamin Pasero 已提交
450
			this.parsed = Color.parse(<string>arg);
E
Erich Gamma 已提交
451
		} else {
B
Benjamin Pasero 已提交
452
			this.parsed = <RGBA>arg;
E
Erich Gamma 已提交
453 454 455 456
		}
		this.str = null;
	}

B
Benjamin Pasero 已提交
457
	private static parse(color: string): RGBA {
E
Erich Gamma 已提交
458 459 460 461 462
		function parseHex(str: string) {
			return parseInt('0x' + str);
		}

		if (color.charAt(0) === '#' && color.length >= 7) {
B
Benjamin Pasero 已提交
463 464 465 466
			let r = parseHex(color.substr(1, 2));
			let g = parseHex(color.substr(3, 2));
			let b = parseHex(color.substr(5, 2));
			let a = color.length === 9 ? parseHex(color.substr(7, 2)) / 0xff : 1;
E
Erich Gamma 已提交
467 468
			return { r, g, b, a };
		}
B
Benjamin Pasero 已提交
469
		return { r: 255, g: 0, b: 0, a: 1 };
E
Erich Gamma 已提交
470 471 472 473
	}

	public toString(): string {
		if (!this.str) {
B
Benjamin Pasero 已提交
474
			let p = this.parsed;
E
Erich Gamma 已提交
475 476 477 478 479
			this.str = `rgba(${p.r}, ${p.g}, ${p.b}, ${+p.a.toFixed(2)})`;
		}
		return this.str;
	}

B
Benjamin Pasero 已提交
480 481 482
	public transparent(factor: number): Color {
		let p = this.parsed;
		return new Color({ r: p.r, g: p.g, b: p.b, a: p.a * factor });
E
Erich Gamma 已提交
483
	}
484 485 486 487 488 489 490 491 492 493

	public opposite(): Color {
		return new Color({
			r: 255 - this.parsed.r,
			g: 255 - this.parsed.g,
			b: 255 - this.parsed.b,
			a : this.parsed.a
		});
	}
}