diff --git a/src/htmlContent/themes-settings.html b/src/htmlContent/themes-settings.html index 6aec089b573..029d4782459 100644 --- a/src/htmlContent/themes-settings.html +++ b/src/htmlContent/themes-settings.html @@ -9,7 +9,7 @@

Themes Settings

diff --git a/src/view/ThemeManager.js b/src/view/ThemeManager.js index afbf3821e31..6f64c63a1cd 100644 --- a/src/view/ThemeManager.js +++ b/src/view/ThemeManager.js @@ -42,6 +42,7 @@ define(function (require, exports, module) { prefs = PreferencesManager.getExtensionPrefs("brackets-themes"); var loadedThemes = {}, + currentTheme = null, defaultTheme = "thor-light-theme", commentRegex = /\/\*([\s\S]*?)\*\//mg, scrollbarsRegex = /::-webkit-scrollbar(?:[\s\S]*?)\{(?:[\s\S]*?)\}/mg, @@ -73,7 +74,7 @@ define(function (require, exports, module) { * dialog, and to properly add a theme into CodeMirror along with the rest of brackets. * * @param {File} file for the theme - * @param {{name: string, className: string, title: string}} options to configure different + * @param {{name: string, title: string}} options to configure different * properties in the theme */ function Theme(file, options) { @@ -86,9 +87,9 @@ define(function (require, exports, module) { // is a theme1.css and a theme1.less that are entirely different themes... this.file = file; - this.name = options.name || (options.title || fileName).toLocaleLowerCase().replace(/[\W]/g, '-'); - this.className = options.className || "theme-" + this.name; - this.displayName = options.title || toDisplayName(fileName); + this.name = options.name || (options.title || fileName).toLocaleLowerCase().replace(/[\W]/g, '-'); + this.displayName = options.title || toDisplayName(fileName); + this.dark = options.dark === true; } @@ -156,7 +157,7 @@ define(function (require, exports, module) { filename: fixPath(theme.file._path) }); - parser.parse("." + theme.className + "{" + content + "}", function (err, tree) { + parser.parse("#editor-holder {" + content + "}", function (err, tree) { if (err) { deferred.reject(err); } else { @@ -197,79 +198,83 @@ define(function (require, exports, module) { /** - * @private - * Get all current theme objects + * Get all current theme objects that are loaded in the editor. * * @return {Array.} collection of the current theme instances */ - function getCurrentThemes() { - return _.map(prefs.get("themes").slice(0), function (item) { - return loadedThemes[item] || loadedThemes[defaultTheme]; - }); + function getCurrentTheme() { + if (!currentTheme) { + currentTheme = loadedThemes[prefs.get("theme")] || loadedThemes[defaultTheme]; + } + + return currentTheme; } /** - * Provides quick access to all available themes + * Gets all available themes that can be loaded in the editor. * @return {Array.} collection of all available themes */ function getAllThemes() { - return _.map(loadedThemes, function (item) { - return item; + return loadedThemes.map(function (theme) { + return theme; }); } /** * @private - * Loads all current themes + * Process and load the current theme into the editor * * @return {$.Promise} promise object resolved with the theme object and all * corresponding new css/less and scrollbar information */ - function loadCurrentThemes() { - var pendingThemes = _.map(getCurrentThemes(), function (theme) { - - return theme && FileUtils.readAsText(theme.file) - .then(function (content) { - var result = extractScrollbars(content); - theme.scrollbar = result.scrollbar; - return result.content; - }) - .then(function (content) { - return lessifyTheme(content, theme); - }) - .then(function (style) { - return ExtensionUtils.addEmbeddedStyleSheet(style); - }) - .then(function (styleNode) { - // Remove after the style has been applied to avoid weird flashes - if (theme.css) { - $(theme.css).remove(); - } + function loadCurrentTheme() { + var theme = getCurrentTheme(); + + var pending = theme && FileUtils.readAsText(theme.file) + .then(function (content) { + var result = extractScrollbars(content); + theme.scrollbar = result.scrollbar; + return result.content; + }) + .then(function (content) { + return lessifyTheme(content, theme); + }) + .then(function (style) { + return ExtensionUtils.addEmbeddedStyleSheet(style); + }) + .then(function (styleNode) { + // Remove after the style has been applied to avoid weird flashes + if (theme.css) { + $(theme.css).remove(); + } - theme.css = styleNode; - return theme; - }); - }); + theme.css = styleNode; + return theme; + }); - return $.when.apply(undefined, pendingThemes); + return $.when(pending); } /** - * Refresh currently loaded themes + * Refresh current theme in the editor * - * @param {boolean} force is to force reload the current themes + * @param {boolean} force is to force reload the current theme */ function refresh(force) { - $.when(force && loadCurrentThemes()).done(function () { + if (force) { + currentTheme = null; + } + + $.when(force && loadCurrentTheme()).done(function () { var editor = EditorManager.getActiveEditor(); if (!editor || !editor._codeMirror) { return; } - var cm = editor._codeMirror; + var cm = editor._codeMirror; ThemeView.setDocumentMode(cm); ThemeView.updateThemes(cm); }); @@ -285,9 +290,9 @@ define(function (require, exports, module) { * @return {$.Promise} promise object resolved with the theme to be loaded from fileName */ function loadFile(fileName, options) { - var deferred = new $.Deferred(), - file = FileSystem.getFileForPath(fileName), - currentThemes = (prefs.get("themes") || []); + var deferred = new $.Deferred(), + file = FileSystem.getFileForPath(fileName), + currentThemeName = prefs.get("theme"); file.exists(function (err, exists) { var theme; @@ -298,9 +303,9 @@ define(function (require, exports, module) { ThemeSettings._setThemes(loadedThemes); // For themes that are loaded after ThemeManager has been loaded, - // we should check if it's the current theme. It is, then we just + // we should check if it's the current theme. If it is, then we just // load it. - if (currentThemes.indexOf(theme.name) !== -1) { + if (currentThemeName === theme.name) { refresh(true); } @@ -317,11 +322,11 @@ define(function (require, exports, module) { /** * Loads a theme from an extension package. * - * @param {Object} themePackage is a package for the theme to be loaded. + * @param {Object} themePackage is a package from the extension manager for the theme to be loaded. * @return {$.Promise} promise object resolved with the theme to be loaded from the pacakge */ function loadPackage(themePackage) { - var fileName = themePackage.path + "/" + themePackage.metadata.theme; + var fileName = themePackage.path + "/" + themePackage.metadata.theme.file; return loadFile(fileName, themePackage.metadata); } @@ -365,31 +370,23 @@ define(function (require, exports, module) { } } - function loadThemesFiles(themes) { + function loadThemeFiles(theme) { // Iterate through each name in the themes and make them theme objects - var deferred = _.map(themes.files, function (themeFile) { - return loadFile(themes.path + "/" + themeFile); + var deferred = theme.files.forEach(function (themeFile) { + return loadFile(theme.path + "/" + themeFile); }); return $.when.apply(undefined, deferred); } FileSystem.getDirectoryForPath(path).getContents(readContent); - return result.then(loadThemesFiles); + return result.then(loadThemeFiles); } - prefs.on("change", "themes", function () { - refresh(true); - ThemeView.updateScrollbars(getCurrentThemes()[0]); - - // Expose event for theme changes - $(exports).trigger("themeChange", getCurrentThemes()); - }); - prefs.on("change", "customScrollbars", function () { refresh(); - ThemeView.updateScrollbars(getCurrentThemes()[0]); + ThemeView.updateScrollbars(getCurrentTheme()); }); prefs.on("change", "fontSize", function () { @@ -421,16 +418,44 @@ define(function (require, exports, module) { refresh(); }); - refresh(true); + + // We need to postpone setting the prefs on change event handler for themes + // to prevent flickering + AppInit.appReady(function () { + prefs.on("change", "theme", function () { + // Save the theme that's about to be replaced to allow for some transition to prevent flicker + var oldTheme = getCurrentTheme(); + + // Refresh editor with the new theme + refresh(true); + + // Process the scrollbars for the editor + ThemeView.updateScrollbars(getCurrentTheme()); + + // Expose event for theme changes + $(exports).trigger("themeChange", getCurrentTheme()); + + // Delay removing the old style element to avoid flashes when transitioning between themes + if (oldTheme) { + setTimeout(function (theme) { + if (theme.css) { + $(theme.css).remove(); + } + }, 0, oldTheme); + } + }); + }); + + ThemeView.updateFonts(); ThemeView.updateScrollbars(); - exports.refresh = refresh; - exports.loadFile = loadFile; - exports.loadPackage = loadPackage; - exports.loadDirectory = loadDirectory; - exports.getCurrentThemes = getCurrentThemes; - exports.getAllThemes = getAllThemes; + exports.refresh = refresh; + exports.loadFile = loadFile; + exports.loadPackage = loadPackage; + exports.loadDirectory = loadDirectory; + exports.getCurrentTheme = getCurrentTheme; + exports.getAllThemes = getAllThemes; // Exposed for testing purposes exports._toDisplayName = toDisplayName; diff --git a/src/view/ThemeSettings.js b/src/view/ThemeSettings.js index e11c4f15d61..0688e413710 100644 --- a/src/view/ThemeSettings.js +++ b/src/view/ThemeSettings.js @@ -49,7 +49,7 @@ define(function (require, exports, module) { "lineHeight": 1.25, "fontFamily": "'SourceCodePro-Medium', MS ゴシック, 'MS Gothic', monospace", "customScrollbars": true, - "themes": ["thor-light-theme"] + "theme": "thor-light-theme" }; @@ -81,11 +81,9 @@ define(function (require, exports, module) { var $template = $(Mustache.render(template, {"settings": currentSettings, "themes": themes, "Strings": Strings})); // Select the correct theme. - _.each(currentSettings.themes, function (item) { - $template - .find("[value='" + item + "']") - .attr("selected", "selected"); - }); + $template + .find("[value='" + currentSettings.theme + "']") + .attr("selected", "selected"); $template .find("[data-toggle=tab].default") @@ -103,16 +101,11 @@ define(function (require, exports, module) { newSettings[attr] = $target.val(); }) .on("change", function () { - var items; var $target = $(":selected", this); var attr = $target.attr("data-target"); if (attr) { - items = $target.map(function (i, item) { - return $(item).val(); - }); - - prefs.set(attr, items.toArray()); + prefs.set(attr, $target.val()); } }); @@ -123,7 +116,7 @@ define(function (require, exports, module) { }); } else if (id === "cancel") { // Make sure we revert any changes to theme selection - prefs.set("themes", currentSettings.themes); + prefs.set("theme", currentSettings.theme); } }); } @@ -140,7 +133,7 @@ define(function (require, exports, module) { * Restores themes to factory settings. */ function restore() { - prefs.set("themes", defaults.themes); + prefs.set("theme", defaults.theme); prefs.set("fontSize", defaults.fontSize + "px"); prefs.set("lineHeight", defaults.lineHeight); prefs.set("fontFamily", defaults.fontFamily); @@ -148,7 +141,7 @@ define(function (require, exports, module) { } - prefs.definePreference("themes", "array", defaults.themes); + prefs.definePreference("theme", "string", defaults.theme); prefs.definePreference("fontSize", "string", defaults.fontSize + "px"); prefs.definePreference("lineHeight", "number", defaults.lineHeight); prefs.definePreference("fontFamily", "string", defaults.fontFamily); diff --git a/src/view/ThemeView.js b/src/view/ThemeView.js index 26611587279..75f8fc13d5b 100644 --- a/src/view/ThemeView.js +++ b/src/view/ThemeView.js @@ -49,24 +49,6 @@ define(function (require, exports, module) { } - /** - * Add theme class to the document to add proper theme styling scoping. - * New class is added, old class is removed. This basically allows - * themeSettings to get nicely cleaned up from the DOM. - */ - function setDocumentTheme() { - var newThemes = (prefs.get("themes") || []); - var oldThemes = (currentThemes || []).slice(0); - currentThemes = newThemes.slice(0); - - // We gotta prefix theme names with "theme" because themes that start with a number - // will not render correctly. Class names that start with a number are invalid - newThemes = _.map(newThemes, function (theme) { return "theme-" + theme; }).join(" "); - oldThemes = _.map(oldThemes, function (theme) { return "theme-" + theme; }).join(" "); - $("html").removeClass(oldThemes).addClass(newThemes); - } - - function updateLineHeight() { clearFonts(); var value = prefs.get("lineHeight"); @@ -113,20 +95,17 @@ define(function (require, exports, module) { * @param {CodeMirror} cm is the CodeMirror instance currently loaded */ function updateThemes(cm) { - var newThemes = (prefs.get("themes") || []).join(" "), - cmThemes = cm.getOption("theme").replace(/[\s]*/, " "); // Normalize themes string + var newTheme = prefs.get("theme"), + cmTheme = (cm.getOption("theme") || "").replace(/[\s]*/, ""); // Normalize themes string // Check if the editor already has the theme applied... - if (cmThemes === newThemes) { + if (cmTheme === newTheme) { return; } // Setup current and further documents to get the new theme... - CodeMirror.defaults.theme = newThemes; - cm.setOption("theme", newThemes); - - // Make sure to update the document theme if a new theme is being set. - setDocumentTheme(); + CodeMirror.defaults.theme = newTheme; + cm.setOption("theme", newTheme); } @@ -139,7 +118,7 @@ define(function (require, exports, module) { function setDocumentMode(cm) { var mode = cm.getDoc().getMode(); var docMode = mode && (mode.helperType || mode.name); - $("body").removeClass("doctype-" + currentDocMode).addClass("doctype-" + docMode); + $("#editor-holder .CodeMirror").removeClass("doctype-" + currentDocMode).addClass("doctype-" + docMode); currentDocMode = docMode; // Update docMode return docMode; }