]> git.lizzy.rs Git - rust.git/commitdiff
Add a setting to use the system theme
authornasso <nassomails@gmail.com>
Sun, 11 Oct 2020 00:53:37 +0000 (02:53 +0200)
committernasso <nassomails@gmail.com>
Tue, 13 Oct 2020 08:46:21 +0000 (10:46 +0200)
src/librustdoc/html/render/mod.rs
src/librustdoc/html/static/settings.css
src/librustdoc/html/static/settings.js
src/librustdoc/html/static/storage.js

index 76334f0213d158d13f39e66b7672b4f898013f2d..a9d4c2cc813df28182c05d2a6b2a8441f60b6314 100644 (file)
@@ -575,7 +575,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())?;
@@ -810,6 +811,7 @@ fn write_shared(
     but.textContent = item;
     but.onclick = function(el) {{
         switchTheme(currentTheme, mainTheme, item, true);
+        useSystemTheme(false);
     }};
     but.onblur = handleThemeButtonsBlur;
     themes.appendChild(but);
@@ -1343,12 +1345,25 @@ fn print(self, f: &mut Buffer) {
 
 #[derive(Debug)]
 enum Setting {
-    Section { description: &'static str, sub_settings: Vec<Setting> },
-    Entry { js_data_name: &'static str, description: &'static str, default_value: bool },
+    Section {
+        description: &'static str,
+        sub_settings: Vec<Setting>,
+    },
+    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!(
                 "<div class='setting-line'>\
@@ -1356,9 +1371,9 @@ fn display(&self) -> String {
                      <div class='sub-settings'>{}</div>
                  </div>",
                 description,
-                sub_settings.iter().map(|s| s.display()).collect::<String>()
+                sub_settings.iter().map(|s| s.display(root_path, suffix)).collect::<String>()
             ),
-            Setting::Entry { ref js_data_name, ref description, ref default_value } => format!(
+            Setting::Toggle { ref js_data_name, ref description, ref default_value } => format!(
                 "<div class='setting-line'>\
                      <label class='toggle'>\
                      <input type='checkbox' id='{}' {}>\
@@ -1370,13 +1385,40 @@ fn display(&self) -> String {
                 if *default_value { " checked" } else { "" },
                 description,
             ),
+            Setting::Select {
+                ref js_data_name,
+                ref description,
+                ref default_value,
+                ref options,
+            } => format!(
+                "<div class='setting-line'>\
+                     <div>{}</div>\
+                     <label class='select-wrapper'>\
+                         <select id='{}' autocomplete='off'>{}</select>\
+                         <img src='{}down-arrow{}.svg' alt='Select item'>\
+                     </label>\
+                 </div>",
+                description,
+                js_data_name,
+                options
+                    .iter()
+                    .map(|opt| format!(
+                        "<option value=\"{}\" {}>{}</option>",
+                        opt.0,
+                        if &opt.0 == *default_value { "selected" } else { "" },
+                        opt.1,
+                    ))
+                    .collect::<String>(),
+                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 }
     }
 }
 
@@ -1389,9 +1431,39 @@ fn from(values: (&'static str, Vec<T>)) -> Setting {
     }
 }
 
-fn settings(root_path: &str, suffix: &str) -> String {
+fn settings(root_path: &str, suffix: &str, themes: &[StylePath]) -> Result<String, Error> {
+    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::<Result<_, Error>>()?;
+
     // (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![
@@ -1413,16 +1485,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!(
         "<h1 class='fqn'>\
-    <span class='in-band'>Rustdoc settings</span>\
-</h1>\
-<div class='settings'>{}</div>\
-<script src='{}settings{}.js'></script>",
-        settings.iter().map(|s| s.display()).collect::<String>(),
+            <span class='in-band'>Rustdoc settings</span>\
+        </h1>\
+        <div class='settings'>{}</div>\
+        <script src='{}settings{}.js'></script>",
+        settings.iter().map(|s| s.display(root_path, suffix)).collect::<String>(),
         root_path,
         suffix
-    )
+    ))
 }
 
 impl Context {
index d03cf7fcc459eaa12fa66c0d96419ad6ccdbc768..7c91f6b7d18ed84b9449ade3fcf281de97040603 100644 (file)
@@ -4,7 +4,6 @@
 }
 
 .setting-line > div {
-       max-width: calc(100% - 74px);
        display: inline-block;
        vertical-align: top;
        font-size: 17px;
        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;
index 427a74c0c87fa3747d3fad992fbb3ffcdea009e2..67dc77330eecaf83f4c0674e0c246ef7e00e6e23 100644 (file)
@@ -2,8 +2,16 @@
 /* global getCurrentValue, updateLocalStorage */
 
 (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) {
     }
 
     function setEvents() {
-        var elems = document.getElementsByClassName("slider");
-        if (!elems || elems.length === 0) {
-            return;
+        var elems = {
+            toggles: document.getElementsByClassName("slider"),
+            selects: document.getElementsByClassName("select-wrapper")
+        };
+
+        if (elems.toggles && elems.toggles.length > 0) {
+            for (var 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);
+                };
+            }
         }
-        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";
+
+        if (elems.selects && elems.selects.length > 0) {
+            for (var 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);
-            };
         }
     }
 
index 0a2fae274fa87ade2260a0cb25e0d3b66e8fc291..3ee693d6eacaba5c44bb02c1145a3211429b5efc 100644 (file)
@@ -118,11 +118,71 @@ function switchTheme(styleElem, mainStyleElem, newTheme, saveTheme) {
     }
 }
 
-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) {
+    // call the function to initialize the theme at least once!
+    updateSystemTheme();
+} else {
+    switchTheme(currentTheme, mainTheme,
+                getCurrentValue("rustdoc-theme") || "light",
+                false);
+}