Skip to content

Commit

Permalink
Merge pull request #387 from cezarsa:grayscale
Browse files Browse the repository at this point in the history
Add config for grayscale icons
  • Loading branch information
PKief committed Feb 13, 2019
2 parents c7ffad8 + aea186a commit 77dc451
Show file tree
Hide file tree
Showing 13 changed files with 310 additions and 7 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ After installation or update you can click on the 'Activate'-button to activate

<img src="https://raw.githubusercontent.com/PKief/vscode-material-icon-theme/master/images/oneclickactivation.png" alt="activation" width="60%">

## Custom icon saturation

If colors do not make you happy you can change icons to have less saturation making them look grayish or completely grayscale by setting saturation to 0:

```json
"material-icon-theme.saturation": 0.5
```

## Commands

Press `Ctrl-Shift-P` to open the command palette and type `Material Icons`.
Expand All @@ -119,6 +127,10 @@ Press `Ctrl-Shift-P` to open the command palette and type `Material Icons`.

- **Restore Default Configuration**: Reset the default configurations of the icon theme.

- **Toggle Grayscale**: Change icons to saturation to 0 making them look grayscale.

- **Change Saturation**: Change the saturation value of the icons.

## Icon sources

* [Material Design Icons](https://materialdesignicons.com/)
Expand Down
16 changes: 16 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@
{
"command": "material-icon-theme.opacity",
"title": "%command.opacity%"
},
{
"command": "material-icon-theme.grayscale",
"title": "%command.grayscale%"
},
{
"command": "material-icon-theme.saturation",
"title": "%command.saturation%"
}
],
"configuration": {
Expand Down Expand Up @@ -157,6 +165,13 @@
"type": "boolean",
"default": false,
"description": "%configuration.hidesExplorerArrows%"
},
"material-icon-theme.saturation": {
"type": "number",
"default": 1,
"minimum": 0,
"maximum": 1,
"description": "%configuration.saturation%"
}
}
}
Expand Down Expand Up @@ -195,3 +210,4 @@
"vscode": "^1.1.29"
}
}

5 changes: 4 additions & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"command.restoreDefaultConfig": "Material Icons: Restore Default Configuration",
"command.hidesExplorerArrows": "Material Icons: Hide Folder Arrows",
"command.opacity": "Material Icons: Change Opacity",
"command.grayscale": "Material Icons: Toggle Grayscale",
"command.saturation": "Material Icons: Change Saturation",
"configuration.title": "Material Icons",
"configuration.files.associations": "Set custom file icon associations.",
"configuration.folders.associations": "Set custom folder icon associations.",
Expand All @@ -25,5 +27,6 @@
"configuration.folders.theme.none": "No folder icons.",
"configuration.folders.color": "Change the color of the folder icons.",
"configuration.hidesExplorerArrows": "Hide explorer arrows before folder.",
"configuration.opacity": "Change the opacity of the icons."
"configuration.opacity": "Change the opacity of the icons.",
"configuration.saturation": "Change the saturation of the icons."
}
53 changes: 53 additions & 0 deletions src/commands/grayscale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import * as vscode from 'vscode';
import * as helpers from './../helpers';
import * as i18n from './../i18n';

/** Command to toggle grayscale. */
export const toggleGrayscale = () => {
return checkGrayscaleStatus()
.then(showQuickPickItems)
.then(handleQuickPickActions)
.catch(err => console.log(err));
};

/** Show QuickPick items to select preferred configuration for grayscale icons. */
const showQuickPickItems = (status: boolean) => {
const on: vscode.QuickPickItem = {
description: i18n.translate('toggleSwitch.on'),
detail: i18n.translate(`grayscale.enableGrayscale`),
label: status ? '\u2714' : '\u25FB'
};
const off: vscode.QuickPickItem = {
description: i18n.translate('toggleSwitch.off'),
detail: i18n.translate(`grayscale.disableGrayscale`),
label: !status ? '\u2714' : '\u25FB'
};
return vscode.window.showQuickPick(
[on, off], {
placeHolder: i18n.translate('grayscale.toggleGrayscale'),
ignoreFocusOut: false,
matchOnDescription: true
});
};

/** Handle the actions from the QuickPick. */
const handleQuickPickActions = (value: vscode.QuickPickItem) => {
if (!value || !value.description) return;
switch (value.description) {
case i18n.translate('toggleSwitch.on'): {
helpers.setThemeConfig('saturation', 0, true);
break;
}
case i18n.translate('toggleSwitch.off'): {
helpers.setThemeConfig('saturation', 1, true);
break;
}
default:
break;
}
};

/** Is grayscale icons enabled? */
export const checkGrayscaleStatus = (): Promise<boolean> => {
return helpers.getMaterialIconsJSON().then((config) => config.options.saturation === 0);
};
16 changes: 15 additions & 1 deletion src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { changeFolderTheme } from './folders';
import { toggleIconPacks } from './iconPacks';
import { changeOpacity } from './opacity';
import { restoreDefaultConfig } from './restoreConfig';
import { toggleGrayscale } from './grayscale';
import { changeSaturation } from './saturation';

// Activate theme
const activateThemeCommand = vscode.commands.registerCommand('material-icon-theme.activateIcons', () => {
Expand Down Expand Up @@ -42,12 +44,24 @@ const changeOpacityCommand = vscode.commands.registerCommand('material-icon-them
changeOpacity();
});

// Toggle grayscale icons
const grayscaleCommand = vscode.commands.registerCommand('material-icon-theme.grayscale', () => {
toggleGrayscale();
});

// Change the saturation of the icons
const changeSaturationCommand = vscode.commands.registerCommand('material-icon-theme.saturation', () => {
changeSaturation();
});

export const commands = [
activateThemeCommand,
toggleIconPacksCommand,
changeFolderThemeCommand,
toggleFolderColorCommand,
restoreDefaultConfigCommand,
hidesExplorerArrowsCommand,
changeOpacityCommand
changeOpacityCommand,
grayscaleCommand,
changeSaturationCommand
];
1 change: 1 addition & 0 deletions src/commands/restoreConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const restoreDefaultConfig = () => {
helpers.setThemeConfig('folders.color', undefined, true);
helpers.setThemeConfig('hidesExplorerArrows', undefined, true);
helpers.setThemeConfig('opacity', undefined, true);
helpers.setThemeConfig('saturation', undefined, true);
helpers.setThemeConfig('files.associations', undefined, true);
helpers.setThemeConfig('folders.associations', undefined, true);
helpers.setThemeConfig('languages.associations', undefined, true);
Expand Down
43 changes: 43 additions & 0 deletions src/commands/saturation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import * as vscode from 'vscode';
import { getDefaultIconOptions, validateSaturationValue } from '../icons';
import * as helpers from './../helpers';
import * as i18n from './../i18n';

/** Command to toggle the folder icons. */
export const changeSaturation = () => {
return getCurrentSaturationValue()
.then(showInput)
.catch(err => console.log(err));
};

/** Show input to enter the saturation value. */
const showInput = (saturation: number) => {
vscode.window.showInputBox({
placeHolder: i18n.translate('saturation.inputPlaceholder'),
ignoreFocusOut: true,
value: String(saturation),
validateInput: validateSaturationInput
}).then(value => setSaturationConfig(+value));
};

/** Validate the saturation value which was inserted by the user. */
const validateSaturationInput = (saturationInput: string) => {
if (!validateSaturationValue(+saturationInput)) {
return i18n.translate('saturation.wrongValue');
}
return undefined;
};

/** Get the current value of the saturation of the icons. */
export const getCurrentSaturationValue = (): Promise<number> => {
const defaultOptions = getDefaultIconOptions();
return helpers.getMaterialIconsJSON().then((config) =>
config.options.saturation === undefined ?
defaultOptions.saturation : config.options.saturation);
};

const setSaturationConfig = (saturation: number) => {
if (saturation !== undefined) {
helpers.setThemeConfig('saturation', saturation, true);
}
};
11 changes: 10 additions & 1 deletion src/i18n/lang-en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,14 @@ export const translation: Translation = {
'confirmReload': 'You have to restart VS Code to activate the changes to the icons.',
'reload': 'Restart',
'outdatedVersion': 'You have to update VS Code to use this command.',
'updateVSCode': 'Update VS Code'
'updateVSCode': 'Update VS Code',
'grayscale': {
'toggleGrayscale': 'Toggle grayscale icons',
'enableGrayscale': 'Enable grayscale icons',
'disableGrayscale': 'Disable grayscale icons'
},
'saturation': {
'inputPlaceholder': 'Saturation value (between 0 and 1)',
'wrongValue': 'The value must be between 0 and 1!',
}
};
132 changes: 132 additions & 0 deletions src/icons/generator/iconSaturation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import * as fs from 'fs';
import * as path from 'path';

/**
* Changes saturation of all icons in the set.
* @param saturation Saturation value.
* @param fileNames Only change the saturation of certain file names.
*/
export const setIconSaturation = (saturation: number, fileNames?: string[]) => {
if (!validateSaturationValue(saturation)) {
return console.error('Invalid saturation value! Saturation must be a decimal number between 0 and 1!');
}

return new Promise((resolve, reject) => {
let iconsPath = path.join(__dirname, '..', '..', '..');
const parentFolder = iconsPath.split(path.sep).pop();
if (parentFolder === 'out') {
iconsPath = path.join(iconsPath, '..');
}
iconsPath = path.join(iconsPath, 'icons');

// read all icon files from the icons folder
try {
(fileNames || fs.readdirSync(iconsPath)).forEach(iconFileName => {
const svgFilePath = path.join(iconsPath, iconFileName);

// Read SVG file
const svg = fs.readFileSync(svgFilePath, 'utf-8');

// Get the root element of the SVG file
const svgRootElement = getSVGRootElement(svg);
if (!svgRootElement) return;

let updatedRootElement: string;
if (saturation < 1) {
updatedRootElement = addFilterAttribute(svgRootElement);
} else {
updatedRootElement = removeFilterAttribute(svgRootElement);
}
let updatedSVG = svg.replace(/<svg[^>]*>/, updatedRootElement);
if (saturation < 1) {
updatedSVG = addFilterElement(updatedSVG, saturation);
} else {
updatedSVG = removeFilterElement(updatedSVG);
}

fs.writeFileSync(svgFilePath, updatedSVG);
resolve();
});
}
catch (e) {
console.log(e);
reject(e);
}
resolve();
});
};

/**
* Get the SVG root element.
* @param svg SVG file as string.
*/
const getSVGRootElement = (svg: string) => {
const result = new RegExp(/<svg[^>]*>/).exec(svg);
if (result.length > 0) {
return result[0];
} else {
return undefined;
}
};

/**
* Add an filter attribute to the SVG icon.
* @param svgRoot Root element of the SVG icon.
*/
const addFilterAttribute = (svgRoot: string) => {
const pattern = new RegExp(/\sfilter="[^"]+?"/);
// if the filter attribute already exists
if (pattern.test(svgRoot)) {
return svgRoot.replace(pattern, ` filter="url(#saturation)"`);
} else {
return svgRoot.replace(/^<svg/, `<svg filter="url(#saturation)"`);
}
};

/**
* Remove the filter attribute of the SVG icon.
* @param svgRoot Root element of the SVG icon.
*/
const removeFilterAttribute = (svgRoot: string) => {
const pattern = new RegExp(/\sfilter="[^"]+?"/);
// check if the filter attribute exists
if (pattern.test(svgRoot)) {
return svgRoot.replace(pattern, '');
}
return svgRoot;
};

/**
* Add filter element to the SVG icon.
* @param svg SVG file as string.
*/
const addFilterElement = (svg: string, value: number) => {
const pattern = new RegExp(/<filter id="saturation".+<\/filter>(.*<\/svg>)/);
const filterElement = `<filter id="saturation"><feColorMatrix type="saturate" values="${value}"/></filter>`;
if (pattern.test(svg)) {
return svg.replace(pattern, `${filterElement}$1`);
} else {
return svg.replace(/<\/svg>/, `${filterElement}</svg>`);
}
return svg;
};

/**
* Remove filter element from the SVG icon.
* @param svg SVG file as string.
*/
const removeFilterElement = (svg: string) => {
const pattern = new RegExp(/<filter id="saturation".+<\/filter>(.*<\/svg>)/);
if (pattern.test(svg)) {
return svg.replace(pattern, `$1`);
}
return svg;
};

/**
* Validate the saturation value.
* @param saturation Saturation value
*/
export const validateSaturationValue = (saturation: number) => {
return saturation !== null && saturation <= 1 && saturation >= 0;
};
1 change: 1 addition & 0 deletions src/icons/generator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './languageGenerator';
export * from './constants';
export * from './jsonGenerator';
export * from './iconOpacity';
export * from './iconSaturation';
Loading

0 comments on commit 77dc451

Please sign in to comment.