diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 22ae7af617f..0608c4e32e0 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -576,7 +576,8 @@ fn after_krate(&mut self, krate: &clean::Crate, cache: &Cache) -> Result<(), Err settings( self.shared.static_root_path.as_deref().unwrap_or("./"), &self.shared.resource_suffix, - ), + &self.shared.style_files, + )?, &style_files, ); self.shared.fs.write(&settings_file, v.as_bytes())?; @@ -811,6 +812,7 @@ fn write_shared( but.textContent = item; but.onclick = function(el) {{ switchTheme(currentTheme, mainTheme, item, true); + useSystemTheme(false); }}; but.onblur = handleThemeButtonsBlur; themes.appendChild(but); @@ -1344,22 +1346,35 @@ fn print(self, f: &mut Buffer) { #[derive(Debug)] enum Setting { - Section { description: &'static str, sub_settings: Vec }, - Entry { js_data_name: &'static str, description: &'static str, default_value: bool }, + Section { + description: &'static str, + sub_settings: Vec, + }, + Toggle { + js_data_name: &'static str, + description: &'static str, + default_value: bool, + }, + Select { + js_data_name: &'static str, + description: &'static str, + default_value: &'static str, + options: Vec<(String, String)>, + }, } impl Setting { - fn display(&self) -> String { + fn display(&self, root_path: &str, suffix: &str) -> String { match *self { - Setting::Section { ref description, ref sub_settings } => format!( + Setting::Section { description, ref sub_settings } => format!( "
\
{}
\
{}
", description, - sub_settings.iter().map(|s| s.display()).collect::() + sub_settings.iter().map(|s| s.display(root_path, suffix)).collect::() ), - Setting::Entry { ref js_data_name, ref description, ref default_value } => format!( + Setting::Toggle { js_data_name, description, default_value } => format!( "
\
", js_data_name, - if *default_value { " checked" } else { "" }, + if default_value { " checked" } else { "" }, description, ), + Setting::Select { js_data_name, description, default_value, ref options } => format!( + "
\ +
{}
\ + \ +
", + description, + js_data_name, + options + .iter() + .map(|opt| format!( + "", + opt.0, + if &opt.0 == default_value { "selected" } else { "" }, + opt.1, + )) + .collect::(), + root_path, + suffix, + ), } } } impl From<(&'static str, &'static str, bool)> for Setting { fn from(values: (&'static str, &'static str, bool)) -> Setting { - Setting::Entry { js_data_name: values.0, description: values.1, default_value: values.2 } + Setting::Toggle { js_data_name: values.0, description: values.1, default_value: values.2 } } } @@ -1390,9 +1427,39 @@ fn from(values: (&'static str, Vec)) -> Setting { } } -fn settings(root_path: &str, suffix: &str) -> String { +fn settings(root_path: &str, suffix: &str, themes: &[StylePath]) -> Result { + let theme_names: Vec<(String, String)> = themes + .iter() + .map(|entry| { + let theme = + try_none!(try_none!(entry.path.file_stem(), &entry.path).to_str(), &entry.path) + .to_string(); + + Ok((theme.clone(), theme)) + }) + .collect::>()?; + // (id, explanation, default value) let settings: &[Setting] = &[ + ( + "Theme preferences", + vec![ + Setting::from(("use-system-theme", "Use system theme", true)), + Setting::Select { + js_data_name: "preferred-dark-theme", + description: "Preferred dark theme", + default_value: "dark", + options: theme_names.clone(), + }, + Setting::Select { + js_data_name: "preferred-light-theme", + description: "Preferred light theme", + default_value: "light", + options: theme_names, + }, + ], + ) + .into(), ( "Auto-hide item declarations", vec![ @@ -1414,16 +1481,17 @@ fn settings(root_path: &str, suffix: &str) -> String { ("line-numbers", "Show line numbers on code examples", false).into(), ("disable-shortcuts", "Disable keyboard shortcuts", false).into(), ]; - format!( + + Ok(format!( "

\ - Rustdoc settings\ -

\ -
{}
\ -", - settings.iter().map(|s| s.display()).collect::(), + Rustdoc settings\ + \ +
{}
\ + ", + settings.iter().map(|s| s.display(root_path, suffix)).collect::(), root_path, suffix - ) + )) } impl Context { diff --git a/src/librustdoc/html/static/settings.css b/src/librustdoc/html/static/settings.css index d03cf7fcc45..4bacd7b245b 100644 --- a/src/librustdoc/html/static/settings.css +++ b/src/librustdoc/html/static/settings.css @@ -4,7 +4,6 @@ } .setting-line > div { - max-width: calc(100% - 74px); display: inline-block; vertical-align: top; font-size: 17px; @@ -30,6 +29,38 @@ display: none; } +.select-wrapper { + float: right; + position: relative; + height: 27px; + min-width: 25%; +} + +.select-wrapper select { + appearance: none; + -moz-appearance: none; + -webkit-appearance: none; + background: none; + border: 2px solid #ccc; + padding-right: 28px; + width: 100%; +} + +.select-wrapper img { + pointer-events: none; + position: absolute; + right: 0; + bottom: 0; + background: #ccc; + height: 100%; + width: 28px; + padding: 0px 4px; +} + +.select-wrapper select option { + color: initial; +} + .slider { position: absolute; cursor: pointer; diff --git a/src/librustdoc/html/static/settings.js b/src/librustdoc/html/static/settings.js index 427a74c0c87..00a01ac30bc 100644 --- a/src/librustdoc/html/static/settings.js +++ b/src/librustdoc/html/static/settings.js @@ -1,30 +1,56 @@ // Local js definitions: -/* global getCurrentValue, updateLocalStorage */ +/* global getCurrentValue, updateLocalStorage, updateSystemTheme */ (function () { - function changeSetting(settingName, isEnabled) { - updateLocalStorage('rustdoc-' + settingName, isEnabled); + function changeSetting(settingName, value) { + updateLocalStorage("rustdoc-" + settingName, value); + + switch (settingName) { + case "preferred-dark-theme": + case "preferred-light-theme": + case "use-system-theme": + updateSystemTheme(); + break; + } } function getSettingValue(settingName) { - return getCurrentValue('rustdoc-' + settingName); + return getCurrentValue("rustdoc-" + settingName); } function setEvents() { - var elems = document.getElementsByClassName("slider"); - if (!elems || elems.length === 0) { - return; - } - for (var i = 0; i < elems.length; ++i) { - var toggle = elems[i].previousElementSibling; - var settingId = toggle.id; - var settingValue = getSettingValue(settingId); - if (settingValue !== null) { - toggle.checked = settingValue === "true"; + var elems = { + toggles: document.getElementsByClassName("slider"), + selects: document.getElementsByClassName("select-wrapper") + }; + var i; + + if (elems.toggles && elems.toggles.length > 0) { + for (i = 0; i < elems.toggles.length; ++i) { + var toggle = elems.toggles[i].previousElementSibling; + var settingId = toggle.id; + var settingValue = getSettingValue(settingId); + if (settingValue !== null) { + toggle.checked = settingValue === "true"; + } + toggle.onchange = function() { + changeSetting(this.id, this.checked); + }; + } + } + + if (elems.selects && elems.selects.length > 0) { + for (i = 0; i < elems.selects.length; ++i) { + var select = elems.selects[i].getElementsByTagName("select")[0]; + var settingId = select.id; + var settingValue = getSettingValue(settingId); + if (settingValue !== null) { + select.value = settingValue; + } + select.onchange = function() { + changeSetting(this.id, this.value); + }; } - toggle.onchange = function() { - changeSetting(this.id, this.checked); - }; } } diff --git a/src/librustdoc/html/static/storage.js b/src/librustdoc/html/static/storage.js index 0a2fae274fa..a027d6845ea 100644 --- a/src/librustdoc/html/static/storage.js +++ b/src/librustdoc/html/static/storage.js @@ -1,8 +1,10 @@ // From rust: /* global resourcesSuffix */ +var darkThemes = ["dark", "ayu"]; var currentTheme = document.getElementById("themeStyle"); var mainTheme = document.getElementById("mainThemeStyle"); +var localStoredTheme = getCurrentValue("rustdoc-theme"); var savedHref = []; @@ -110,19 +112,90 @@ function switchTheme(styleElem, mainStyleElem, newTheme, saveTheme) { }); if (found === true) { styleElem.href = newHref; - // If this new value comes from a system setting or from the previously saved theme, no - // need to save it. + // If this new value comes from a system setting or from the previously + // saved theme, no need to save it. if (saveTheme === true) { updateLocalStorage("rustdoc-theme", newTheme); } } } -function getSystemValue() { - var property = getComputedStyle(document.documentElement).getPropertyValue('content'); - return property.replace(/[\"\']/g, ""); +function useSystemTheme(value) { + if (value === undefined) { + value = true; + } + + updateLocalStorage("rustdoc-use-system-theme", value); + + // update the toggle if we're on the settings page + var toggle = document.getElementById("use-system-theme"); + if (toggle && toggle instanceof HTMLInputElement) { + toggle.checked = value; + } } -switchTheme(currentTheme, mainTheme, - getCurrentValue("rustdoc-theme") || getSystemValue() || "light", - false); +var updateSystemTheme = (function() { + if (!window.matchMedia) { + // fallback to the CSS computed value + return function() { + let cssTheme = getComputedStyle(document.documentElement) + .getPropertyValue('content'); + + switchTheme( + currentTheme, + mainTheme, + JSON.parse(cssTheme) || light, + true + ); + }; + } + + // only listen to (prefers-color-scheme: dark) because light is the default + var mql = window.matchMedia("(prefers-color-scheme: dark)"); + + function handlePreferenceChange(mql) { + // maybe the user has disabled the setting in the meantime! + if (getCurrentValue("rustdoc-use-system-theme") !== "false") { + var lightTheme = getCurrentValue("rustdoc-preferred-light-theme") || "light"; + var darkTheme = getCurrentValue("rustdoc-preferred-dark-theme") || "dark"; + + if (mql.matches) { + // prefers a dark theme + switchTheme(currentTheme, mainTheme, darkTheme, true); + } else { + // prefers a light theme, or has no preference + switchTheme(currentTheme, mainTheme, lightTheme, true); + } + + // note: we save the theme so that it doesn't suddenly change when + // the user disables "use-system-theme" and reloads the page or + // navigates to another page + } + } + + mql.addListener(handlePreferenceChange); + + return function() { + handlePreferenceChange(mql); + }; +})(); + +if (getCurrentValue("rustdoc-use-system-theme") !== "false" && window.matchMedia) { + // update the preferred dark theme if the user is already using a dark theme + // See https://github.com/rust-lang/rust/pull/77809#issuecomment-707875732 + if (getCurrentValue("rustdoc-use-system-theme") === null + && getCurrentValue("rustdoc-preferred-dark-theme") === null + && darkThemes.indexOf(localStoredTheme) >= 0) { + updateLocalStorage("rustdoc-preferred-dark-theme", localStoredTheme); + } + + // call the function to initialize the theme at least once! + updateSystemTheme(); +} else { + switchTheme( + currentTheme, + mainTheme, + getCurrentValue("rustdoc-theme") || "light", + false + ); +}