Skip to content

Commit 45f6762

Browse files
committed
Add a setting to use the system theme
1 parent 790d19c commit 45f6762

File tree

4 files changed

+232
-36
lines changed

4 files changed

+232
-36
lines changed

src/librustdoc/html/render/mod.rs

Lines changed: 88 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,8 @@ impl FormatRenderer for Context {
575575
settings(
576576
self.shared.static_root_path.as_deref().unwrap_or("./"),
577577
&self.shared.resource_suffix,
578-
),
578+
&self.shared.style_files,
579+
)?,
579580
&style_files,
580581
);
581582
self.shared.fs.write(&settings_file, v.as_bytes())?;
@@ -810,6 +811,7 @@ themePicker.onblur = handleThemeButtonsBlur;
810811
but.textContent = item;
811812
but.onclick = function(el) {{
812813
switchTheme(currentTheme, mainTheme, item, true);
814+
useSystemTheme(false);
813815
}};
814816
but.onblur = handleThemeButtonsBlur;
815817
themes.appendChild(but);
@@ -1343,22 +1345,35 @@ impl AllTypes {
13431345

13441346
#[derive(Debug)]
13451347
enum Setting {
1346-
Section { description: &'static str, sub_settings: Vec<Setting> },
1347-
Entry { js_data_name: &'static str, description: &'static str, default_value: bool },
1348+
Section {
1349+
description: &'static str,
1350+
sub_settings: Vec<Setting>,
1351+
},
1352+
Toggle {
1353+
js_data_name: &'static str,
1354+
description: &'static str,
1355+
default_value: bool,
1356+
},
1357+
Select {
1358+
js_data_name: &'static str,
1359+
description: &'static str,
1360+
default_value: &'static str,
1361+
options: Vec<(String, String)>,
1362+
},
13481363
}
13491364

13501365
impl Setting {
1351-
fn display(&self) -> String {
1366+
fn display(&self, root_path: &str, suffix: &str) -> String {
13521367
match *self {
13531368
Setting::Section { ref description, ref sub_settings } => format!(
13541369
"<div class='setting-line'>\
13551370
<div class='title'>{}</div>\
13561371
<div class='sub-settings'>{}</div>
13571372
</div>",
13581373
description,
1359-
sub_settings.iter().map(|s| s.display()).collect::<String>()
1374+
sub_settings.iter().map(|s| s.display(root_path, suffix)).collect::<String>()
13601375
),
1361-
Setting::Entry { ref js_data_name, ref description, ref default_value } => format!(
1376+
Setting::Toggle { ref js_data_name, ref description, ref default_value } => format!(
13621377
"<div class='setting-line'>\
13631378
<label class='toggle'>\
13641379
<input type='checkbox' id='{}' {}>\
@@ -1370,13 +1385,40 @@ impl Setting {
13701385
if *default_value { " checked" } else { "" },
13711386
description,
13721387
),
1388+
Setting::Select {
1389+
ref js_data_name,
1390+
ref description,
1391+
ref default_value,
1392+
ref options,
1393+
} => format!(
1394+
"<div class='setting-line'>\
1395+
<div>{}</div>\
1396+
<label class='select-wrapper'>\
1397+
<select id='{}' autocomplete='off'>{}</select>\
1398+
<img src='{}down-arrow{}.svg' alt='Select item'>\
1399+
</label>\
1400+
</div>",
1401+
description,
1402+
js_data_name,
1403+
options
1404+
.iter()
1405+
.map(|opt| format!(
1406+
"<option value=\"{}\" {}>{}</option>",
1407+
opt.0,
1408+
if &opt.0 == *default_value { "selected" } else { "" },
1409+
opt.1,
1410+
))
1411+
.collect::<String>(),
1412+
root_path,
1413+
suffix,
1414+
),
13731415
}
13741416
}
13751417
}
13761418

13771419
impl From<(&'static str, &'static str, bool)> for Setting {
13781420
fn from(values: (&'static str, &'static str, bool)) -> Setting {
1379-
Setting::Entry { js_data_name: values.0, description: values.1, default_value: values.2 }
1421+
Setting::Toggle { js_data_name: values.0, description: values.1, default_value: values.2 }
13801422
}
13811423
}
13821424

@@ -1389,9 +1431,39 @@ impl<T: Into<Setting>> From<(&'static str, Vec<T>)> for Setting {
13891431
}
13901432
}
13911433

1392-
fn settings(root_path: &str, suffix: &str) -> String {
1434+
fn settings(root_path: &str, suffix: &str, themes: &[StylePath]) -> Result<String, Error> {
1435+
let theme_names: Vec<(String, String)> = themes
1436+
.iter()
1437+
.map(|entry| {
1438+
let theme =
1439+
try_none!(try_none!(entry.path.file_stem(), &entry.path).to_str(), &entry.path)
1440+
.to_string();
1441+
1442+
Ok((theme.clone(), theme))
1443+
})
1444+
.collect::<Result<_, Error>>()?;
1445+
13931446
// (id, explanation, default value)
13941447
let settings: &[Setting] = &[
1448+
(
1449+
"Theme preferences",
1450+
vec![
1451+
Setting::from(("use-system-theme", "Use system theme", true)),
1452+
Setting::Select {
1453+
js_data_name: "preferred-dark-theme",
1454+
description: "Preferred dark theme",
1455+
default_value: "dark",
1456+
options: theme_names.clone(),
1457+
},
1458+
Setting::Select {
1459+
js_data_name: "preferred-light-theme",
1460+
description: "Preferred light theme",
1461+
default_value: "light",
1462+
options: theme_names,
1463+
},
1464+
],
1465+
)
1466+
.into(),
13951467
(
13961468
"Auto-hide item declarations",
13971469
vec![
@@ -1413,16 +1485,17 @@ fn settings(root_path: &str, suffix: &str) -> String {
14131485
("line-numbers", "Show line numbers on code examples", false).into(),
14141486
("disable-shortcuts", "Disable keyboard shortcuts", false).into(),
14151487
];
1416-
format!(
1488+
1489+
Ok(format!(
14171490
"<h1 class='fqn'>\
1418-
<span class='in-band'>Rustdoc settings</span>\
1419-
</h1>\
1420-
<div class='settings'>{}</div>\
1421-
<script src='{}settings{}.js'></script>",
1422-
settings.iter().map(|s| s.display()).collect::<String>(),
1491+
<span class='in-band'>Rustdoc settings</span>\
1492+
</h1>\
1493+
<div class='settings'>{}</div>\
1494+
<script src='{}settings{}.js'></script>",
1495+
settings.iter().map(|s| s.display(root_path, suffix)).collect::<String>(),
14231496
root_path,
14241497
suffix
1425-
)
1498+
))
14261499
}
14271500

14281501
impl Context {

src/librustdoc/html/static/settings.css

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
}
55

66
.setting-line > div {
7-
max-width: calc(100% - 74px);
87
display: inline-block;
98
vertical-align: top;
109
font-size: 17px;
@@ -30,6 +29,45 @@
3029
display: none;
3130
}
3231

32+
.select-wrapper {
33+
float: right;
34+
35+
position: relative;
36+
37+
height: 27px;
38+
min-width: 25%;
39+
}
40+
41+
.select-wrapper select {
42+
appearance: none;
43+
-moz-appearance: none;
44+
-webkit-appearance: none;
45+
46+
background: none;
47+
border: 2px solid #ccc;
48+
padding-right: 28px;
49+
50+
width: 100%;
51+
}
52+
53+
.select-wrapper img {
54+
pointer-events: none;
55+
56+
position: absolute;
57+
right: 0;
58+
bottom: 0;
59+
60+
background: #ccc;
61+
62+
height: 100%;
63+
width: 28px;
64+
padding: 0px 4px;
65+
}
66+
67+
.select-wrapper select option {
68+
color: initial;
69+
}
70+
3371
.slider {
3472
position: absolute;
3573
cursor: pointer;

src/librustdoc/html/static/settings.js

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,54 @@
22
/* global getCurrentValue, updateLocalStorage */
33

44
(function () {
5-
function changeSetting(settingName, isEnabled) {
6-
updateLocalStorage('rustdoc-' + settingName, isEnabled);
5+
function changeSetting(settingName, value) {
6+
updateLocalStorage('rustdoc-' + settingName, value);
7+
8+
switch (settingName) {
9+
case 'preferred-dark-theme':
10+
case 'preferred-light-theme':
11+
case 'use-system-theme':
12+
updateSystemTheme();
13+
break;
14+
}
715
}
816

917
function getSettingValue(settingName) {
1018
return getCurrentValue('rustdoc-' + settingName);
1119
}
1220

1321
function setEvents() {
14-
var elems = document.getElementsByClassName("slider");
15-
if (!elems || elems.length === 0) {
16-
return;
22+
var elems = {
23+
toggles: document.getElementsByClassName("slider"),
24+
selects: document.getElementsByClassName("select-wrapper")
25+
};
26+
27+
if (elems.toggles && elems.toggles.length > 0) {
28+
for (var i = 0; i < elems.toggles.length; ++i) {
29+
var toggle = elems.toggles[i].previousElementSibling;
30+
var settingId = toggle.id;
31+
var settingValue = getSettingValue(settingId);
32+
if (settingValue !== null) {
33+
toggle.checked = settingValue === "true";
34+
}
35+
toggle.onchange = function() {
36+
changeSetting(this.id, this.checked);
37+
};
38+
}
1739
}
18-
for (var i = 0; i < elems.length; ++i) {
19-
var toggle = elems[i].previousElementSibling;
20-
var settingId = toggle.id;
21-
var settingValue = getSettingValue(settingId);
22-
if (settingValue !== null) {
23-
toggle.checked = settingValue === "true";
40+
41+
if (elems.selects && elems.selects.length > 0) {
42+
for (var i = 0; i < elems.selects.length; ++i) {
43+
var select = elems.selects[i].getElementsByTagName('select')[0];
44+
var settingId = select.id;
45+
var settingValue = getSettingValue(settingId);
46+
if (settingValue !== null) {
47+
select.value = settingValue;
48+
}
49+
select.onchange = function() {
50+
changeSetting(this.id, this.value);
51+
};
2452
}
25-
toggle.onchange = function() {
26-
changeSetting(this.id, this.checked);
27-
};
2853
}
2954
}
3055

src/librustdoc/html/static/storage.js

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,71 @@ function switchTheme(styleElem, mainStyleElem, newTheme, saveTheme) {
118118
}
119119
}
120120

121-
function getSystemValue() {
122-
var property = getComputedStyle(document.documentElement).getPropertyValue('content');
123-
return property.replace(/[\"\']/g, "");
121+
function useSystemTheme(value) {
122+
if (value === undefined) {
123+
value = true;
124+
}
125+
126+
updateLocalStorage("rustdoc-use-system-theme", value);
127+
128+
// update the toggle if we're on the settings page
129+
var toggle = document.getElementById("use-system-theme");
130+
if (toggle && toggle instanceof HTMLInputElement) {
131+
toggle.checked = value;
132+
}
124133
}
125134

126-
switchTheme(currentTheme, mainTheme,
127-
getCurrentValue("rustdoc-theme") || getSystemValue() || "light",
128-
false);
135+
var updateSystemTheme = (function() {
136+
if (!window.matchMedia) {
137+
// fallback to the CSS computed value
138+
return function() {
139+
let cssTheme = getComputedStyle(document.documentElement)
140+
.getPropertyValue('content');
141+
142+
switchTheme(
143+
currentTheme,
144+
mainTheme,
145+
JSON.parse(cssTheme) || light,
146+
true
147+
);
148+
};
149+
}
150+
151+
// only listen to (prefers-color-scheme: dark) because light is the default
152+
var mql = window.matchMedia("(prefers-color-scheme: dark)");
153+
154+
function handlePreferenceChange(mql) {
155+
// maybe the user has disabled the setting in the meantime!
156+
if (getCurrentValue("rustdoc-use-system-theme") !== "false") {
157+
var lightTheme = getCurrentValue("rustdoc-preferred-light-theme") || "light";
158+
var darkTheme = getCurrentValue("rustdoc-preferred-dark-theme") || "dark";
159+
160+
if (mql.matches) {
161+
// prefers a dark theme
162+
switchTheme(currentTheme, mainTheme, darkTheme, true);
163+
} else {
164+
// prefers a light theme, or has no preference
165+
switchTheme(currentTheme, mainTheme, lightTheme, true);
166+
}
167+
168+
// note: we save the theme so that it doesn't suddenly change when
169+
// the user disables "use-system-theme" and reloads the page or
170+
// navigates to another page
171+
}
172+
}
173+
174+
mql.addListener(handlePreferenceChange);
175+
176+
return function() {
177+
handlePreferenceChange(mql);
178+
};
179+
})();
180+
181+
if (getCurrentValue("rustdoc-use-system-theme") !== "false" && window.matchMedia) {
182+
// call the function to initialize the theme at least once!
183+
updateSystemTheme();
184+
} else {
185+
switchTheme(currentTheme, mainTheme,
186+
getCurrentValue("rustdoc-theme") || "light",
187+
false);
188+
}

0 commit comments

Comments
 (0)