diff --git a/src/librustdoc/externalfiles.rs b/src/librustdoc/externalfiles.rs index f8320330ad265..420e0b2e80706 100644 --- a/src/librustdoc/externalfiles.rs +++ b/src/librustdoc/externalfiles.rs @@ -14,7 +14,7 @@ use std::str; use html::markdown::{Markdown, RenderType}; #[derive(Clone)] -pub struct ExternalHtml{ +pub struct ExternalHtml { /// Content that will be included inline in the <head> section of a /// rendered Markdown file or generated documentation pub in_header: String, diff --git a/src/librustdoc/html/layout.rs b/src/librustdoc/html/layout.rs index 3a18c6b8a809e..8395b0686e1b0 100644 --- a/src/librustdoc/html/layout.rs +++ b/src/librustdoc/html/layout.rs @@ -10,6 +10,7 @@ use std::fmt; use std::io; +use std::path::PathBuf; use externalfiles::ExternalHtml; @@ -31,7 +32,7 @@ pub struct Page<'a> { pub fn render<T: fmt::Display, S: fmt::Display>( dst: &mut io::Write, layout: &Layout, page: &Page, sidebar: &S, t: &T, - css_file_extension: bool) + css_file_extension: bool, themes: &[PathBuf]) -> io::Result<()> { write!(dst, @@ -47,8 +48,11 @@ r##"<!DOCTYPE html> <title>{title}</title> <link rel="stylesheet" type="text/css" href="{root_path}normalize.css"> - <link rel="stylesheet" type="text/css" href="{root_path}rustdoc.css"> - <link rel="stylesheet" type="text/css" href="{root_path}main.css"> + <link rel="stylesheet" type="text/css" href="{root_path}rustdoc.css" id="mainThemeStyle"> + {themes} + <link rel="stylesheet" type="text/css" href="{root_path}dark.css"> + <link rel="stylesheet" type="text/css" href="{root_path}main.css" id="themeStyle"> + <script src="{root_path}storage.js"></script> {css_extension} {favicon} @@ -70,6 +74,11 @@ r##"<!DOCTYPE html> {sidebar} </nav> + <button id="theme-picker"> + <img src="{root_path}brush.svg" width="18" alt="Pick another theme!"> + <div id="theme-choices"></div> + </button> + <script src="{root_path}theme.js"></script> <nav class="sub"> <form class="search-form js-only"> <div class="search-container"> @@ -176,6 +185,12 @@ r##"<!DOCTYPE html> after_content = layout.external_html.after_content, sidebar = *sidebar, krate = layout.krate, + themes = themes.iter() + .filter_map(|t| t.file_stem()) + .filter_map(|t| t.to_str()) + .map(|t| format!(r#"<link rel="stylesheet" type="text/css" href="{}{}">"#, + page.root_path, t)) + .collect::<String>(), ) } diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs index cfa09ea30a8be..4a56dbac50ec3 100644 --- a/src/librustdoc/html/render.rs +++ b/src/librustdoc/html/render.rs @@ -132,6 +132,8 @@ pub struct SharedContext { /// This flag indicates whether listings of modules (in the side bar and documentation itself) /// should be ordered alphabetically or in order of appearance (in the source code). pub sort_modules_alphabetically: bool, + /// Additional themes to be added to the generated docs. + pub themes: Vec<PathBuf>, } impl SharedContext { @@ -219,6 +221,17 @@ impl Error { } } +macro_rules! try_none { + ($e:expr, $file:expr) => ({ + use std::io; + match $e { + Some(e) => e, + None => return Err(Error::new(io::Error::new(io::ErrorKind::Other, "not found"), + $file)) + } + }) +} + macro_rules! try_err { ($e:expr, $file:expr) => ({ match $e { @@ -489,7 +502,8 @@ pub fn run(mut krate: clean::Crate, renderinfo: RenderInfo, render_type: RenderType, sort_modules_alphabetically: bool, - deny_render_differences: bool) -> Result<(), Error> { + deny_render_differences: bool, + themes: Vec<PathBuf>) -> Result<(), Error> { let src_root = match krate.src { FileName::Real(ref p) => match p.parent() { Some(p) => p.to_path_buf(), @@ -513,6 +527,7 @@ pub fn run(mut krate: clean::Crate, markdown_warnings: RefCell::new(vec![]), created_dirs: RefCell::new(FxHashSet()), sort_modules_alphabetically, + themes, }; // If user passed in `--playground-url` arg, we fill in crate name here @@ -859,12 +874,65 @@ fn write_shared(cx: &Context, // Add all the static files. These may already exist, but we just // overwrite them anyway to make sure that they're fresh and up-to-date. - write(cx.dst.join("main.js"), - include_bytes!("static/main.js"))?; write(cx.dst.join("rustdoc.css"), include_bytes!("static/rustdoc.css"))?; + + // To avoid "main.css" to be overwritten, we'll first run over the received themes and only + // then we'll run over the "official" styles. + let mut themes: HashSet<String> = HashSet::new(); + + for entry in &cx.shared.themes { + let mut content = Vec::with_capacity(100000); + + let mut f = try_err!(File::open(&entry), &entry); + try_err!(f.read_to_end(&mut content), &entry); + write(cx.dst.join(try_none!(entry.file_name(), &entry)), content.as_slice())?; + themes.insert(try_none!(try_none!(entry.file_stem(), &entry).to_str(), &entry).to_owned()); + } + + write(cx.dst.join("brush.svg"), + include_bytes!("static/brush.svg"))?; write(cx.dst.join("main.css"), - include_bytes!("static/styles/main.css"))?; + include_bytes!("static/themes/main.css"))?; + themes.insert("main".to_owned()); + write(cx.dst.join("dark.css"), + include_bytes!("static/themes/dark.css"))?; + themes.insert("dark".to_owned()); + + let mut themes: Vec<&String> = themes.iter().collect(); + themes.sort(); + // To avoid theme switch latencies as much as possible, we put everything theme related + // at the beginning of the html files into another js file. + write(cx.dst.join("theme.js"), format!( +r#"var themes = document.getElementById("theme-choices"); +var themePicker = document.getElementById("theme-picker"); +themePicker.onclick = function() {{ + if (themes.style.display === "block") {{ + themes.style.display = "none"; + themePicker.style.borderBottomRightRadius = "3px"; + themePicker.style.borderBottomLeftRadius = "3px"; + }} else {{ + themes.style.display = "block"; + themePicker.style.borderBottomRightRadius = "0"; + themePicker.style.borderBottomLeftRadius = "0"; + }} +}}; +[{}].forEach(function(item) {{ + var div = document.createElement('div'); + div.innerHTML = item; + div.onclick = function(el) {{ + switchTheme(currentTheme, mainTheme, item); + }}; + themes.appendChild(div); +}}); +"#, themes.iter() + .map(|s| format!("\"{}\"", s)) + .collect::<Vec<String>>() + .join(",")).as_bytes())?; + + write(cx.dst.join("main.js"), include_bytes!("static/main.js"))?; + write(cx.dst.join("storage.js"), include_bytes!("static/storage.js"))?; + if let Some(ref css) = cx.shared.css_file_extension { let out = cx.dst.join("theme.css"); try_err!(fs::copy(css, out), css); @@ -1156,7 +1224,8 @@ impl<'a> SourceCollector<'a> { }; layout::render(&mut w, &self.scx.layout, &page, &(""), &Source(contents), - self.scx.css_file_extension.is_some())?; + self.scx.css_file_extension.is_some(), + &self.scx.themes)?; w.flush()?; self.scx.local_sources.insert(p.clone(), href); Ok(()) @@ -1520,7 +1589,8 @@ impl Context { layout::render(writer, &self.shared.layout, &page, &Sidebar{ cx: self, item: it }, &Item{ cx: self, item: it }, - self.shared.css_file_extension.is_some())?; + self.shared.css_file_extension.is_some(), + &self.shared.themes)?; } else { let mut url = self.root_path(); if let Some(&(ref names, ty)) = cache().paths.get(&it.def_id) { diff --git a/src/librustdoc/html/static/brush.svg b/src/librustdoc/html/static/brush.svg new file mode 100644 index 0000000000000..072264a640830 --- /dev/null +++ b/src/librustdoc/html/static/brush.svg @@ -0,0 +1 @@ +<?xml version="1.0" ?><svg height="1792" viewBox="0 0 1792 1792" width="1792" xmlns="http://www.w3.org/2000/svg"><path d="M1615 0q70 0 122.5 46.5t52.5 116.5q0 63-45 151-332 629-465 752-97 91-218 91-126 0-216.5-92.5t-90.5-219.5q0-128 92-212l638-579q59-54 130-54zm-909 1034q39 76 106.5 130t150.5 76l1 71q4 213-129.5 347t-348.5 134q-123 0-218-46.5t-152.5-127.5-86.5-183-29-220q7 5 41 30t62 44.5 59 36.5 46 17q41 0 55-37 25-66 57.5-112.5t69.5-76 88-47.5 103-25.5 125-10.5z"/></svg> \ No newline at end of file diff --git a/src/librustdoc/html/static/main.js b/src/librustdoc/html/static/main.js index a9a5bd5de0552..659bed18b0031 100644 --- a/src/librustdoc/html/static/main.js +++ b/src/librustdoc/html/static/main.js @@ -122,6 +122,10 @@ } } document.getElementsByTagName("body")[0].style.marginTop = '45px'; + var themePicker = document.getElementById("theme-picker"); + if (themePicker) { + themePicker.style.position = "fixed"; + } } function hideSidebar() { @@ -136,6 +140,10 @@ filler.remove(); } document.getElementsByTagName("body")[0].style.marginTop = ''; + var themePicker = document.getElementById("theme-picker"); + if (themePicker) { + themePicker.style.position = "absolute"; + } } // used for special search precedence @@ -1532,7 +1540,9 @@ ul.appendChild(li); } div.appendChild(ul); - sidebar.appendChild(div); + if (sidebar) { + sidebar.appendChild(div); + } } block("primitive", "Primitive Types"); diff --git a/src/librustdoc/html/static/rustdoc.css b/src/librustdoc/html/static/rustdoc.css index 34b04de86735e..c1ca86e3292a4 100644 --- a/src/librustdoc/html/static/rustdoc.css +++ b/src/librustdoc/html/static/rustdoc.css @@ -360,7 +360,8 @@ ul.item-list > li > .out-of-band { } h4 > code, h3 > code, .invisible > code { - position: inherit; + max-width: calc(100% - 41px); + display: block; } .in-band, code { @@ -376,6 +377,7 @@ h4 > code, h3 > code, .invisible > code { margin: 0px; padding: 0px; display: inline-block; + max-width: calc(100% - 43px); } .in-band > code { @@ -1140,3 +1142,37 @@ kbd { border-radius: 3px; box-shadow: inset 0 -1px 0; } + +#theme-picker { + position: absolute; + left: 211px; + top: 17px; + padding: 4px; + border: 1px solid; + border-radius: 3px; + cursor: pointer; +} + +#theme-choices { + display: none; + position: absolute; + left: -1px; + top: 30px; + border: 1px solid; + border-radius: 3px; + z-index: 1; +} + +#theme-choices > div { + border-top: 1px solid; + padding: 4px; + text-align: center; +} + +@media (max-width: 700px) { + #theme-picker { + left: 109px; + top: 7px; + z-index: 1; + } +} diff --git a/src/librustdoc/html/static/storage.js b/src/librustdoc/html/static/storage.js new file mode 100644 index 0000000000000..0aa1065b3786a --- /dev/null +++ b/src/librustdoc/html/static/storage.js @@ -0,0 +1,36 @@ +/*! + * Copyright 2018 The Rust Project Developers. See the COPYRIGHT + * file at the top-level directory of this distribution and at + * http://rust-lang.org/COPYRIGHT. + * + * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or + * http://www.apache.org/licenses/LICENSE-2.0> or the MIT license + * <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your + * option. This file may not be copied, modified, or distributed + * except according to those terms. + */ + +var currentTheme = document.getElementById("themeStyle"); +var mainTheme = document.getElementById("mainThemeStyle"); + +function updateLocalStorage(name, value) { + if (typeof(Storage) !== "undefined") { + localStorage[name] = value; + } else { + // No Web Storage support so we do nothing + } +} + +function getCurrentValue(name) { + if (typeof(Storage) !== "undefined" && localStorage[name] !== undefined) { + return localStorage[name]; + } + return null; +} + +function switchTheme(styleElem, mainStyleElem, newTheme) { + styleElem.href = mainStyleElem.href.replace("rustdoc.css", newTheme + ".css"); + updateLocalStorage('theme', newTheme); +} + +switchTheme(currentTheme, mainTheme, getCurrentValue('theme') || 'main'); diff --git a/src/librustdoc/html/static/themes/dark.css b/src/librustdoc/html/static/themes/dark.css new file mode 100644 index 0000000000000..05ac066039656 --- /dev/null +++ b/src/librustdoc/html/static/themes/dark.css @@ -0,0 +1,384 @@ +/** + * Copyright 2015 The Rust Project Developers. See the COPYRIGHT + * file at the top-level directory of this distribution and at + * http://rust-lang.org/COPYRIGHT. + * + * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or + * http://www.apache.org/licenses/LICENSE-2.0> or the MIT license + * <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your + * option. This file may not be copied, modified, or distributed + * except according to those terms. + */ + +body { + background-color: #353535; + color: #ddd; +} + +h1, h2, h3:not(.impl):not(.method):not(.type):not(.tymethod), h4:not(.method):not(.type):not(.tymethod) { + color: #ddd; +} +h1.fqn { + border-bottom-color: #d2d2d2; +} +h2, h3:not(.impl):not(.method):not(.type):not(.tymethod), h4:not(.method):not(.type):not(.tymethod) { + border-bottom-color: #d2d2d2; +} + +.in-band { + background-color: #353535; +} + +.invisible { + background: rgba(0, 0, 0, 0); +} + +.docblock code, .docblock-short code { + background-color: #2A2A2A; +} +pre { + background-color: #2A2A2A; +} + +.sidebar { + background-color: #505050; +} + +.sidebar .current { + background-color: #333; +} + +.source .sidebar { + background-color: #353535; +} + +.sidebar .location { + border-color: #fff; + background: #575757; + color: #DDD; +} + +.sidebar .version { + border-bottom-color: #DDD; +} + +.sidebar-title { + border-top-color: #777; + border-bottom-color: #777; +} + +.block a:hover { + background: #444; +} + +.line-numbers span { color: #3B91E2; } +.line-numbers .line-highlighted { + background-color: #0a042f !important; +} + +.docblock h1, .docblock h2, .docblock h3, .docblock h4, .docblock h5 { + border-bottom-color: #DDD; +} + +.docblock table { + border-color: #ddd; +} + +.docblock table td { + border-top-color: #ddd; + border-bottom-color: #ddd; +} + +.docblock table th { + border-top-color: #ddd; + border-bottom-color: #ddd; +} + +:target { background: #494a3d; } + +:target > .in-band { + background: #494a3d; +} + +.content .method .where, +.content .fn .where, +.content .where.fmt-newline { + color: #ddd; +} + +.content .highlighted { + color: #eee !important; + background-color: #333; +} +.content .highlighted a, .content .highlighted span { color: #eee !important; } +.content .highlighted.trait { background-color: #013191; } +.content .highlighted.mod { background-color: #803a1b; } +.content .highlighted.externcrate { background-color: #396bac; } +.content .highlighted.enum { background-color: #5b4e68; } +.content .highlighted.struct { background-color: #194e9f; } +.content .highlighted.fn, +.content .highlighted.method, +.content .highlighted.tymethod { background-color: #4950ed; } +.content .highlighted.type { background-color: #38902c; } +.content .highlighted.foreigntype { background-color: #b200d6; } +.content .highlighted.macro { background-color: #217d1c; } +.content .highlighted.constant, +.content .highlighted.static { background-color: #0063cc; } +.content .highlighted.primitive { background-color: #00708a; } + +.content span.enum, .content a.enum, .block a.current.enum { color: #82b089; } +.content span.struct, .content a.struct, .block a.current.struct { color: #ff794d; } +.content span.type, .content a.type, .block a.current.type { color: #ff7f00; } +.content span.foreigntype, .content a.foreigntype, .block a.current.foreigntype { color: #dd7de8; } +.content span.macro, .content a.macro, .block a.current.macro { color: #09bd00; } +.content span.union, .content a.union, .block a.current.union { color: #a6ae37; } +.content span.constant, .content a.constant, .block a.current.constant, +.content span.static, .content a.static, .block a.current.static { color: #82a5c9; } +.content span.primitive, .content a.primitive, .block a.current.primitive { color: #43aec7; } +.content span.externcrate, +.content span.mod, .content a.mod, .block a.current.mod { color: #bda000; } +.content span.trait, .content a.trait, .block a.current.trait { color: #b78cf2; } +.content span.fn, .content a.fn, .block a.current.fn, +.content span.method, .content a.method, .block a.current.method, +.content span.tymethod, .content a.tymethod, .block a.current.tymethod, +.content .fnname{ color: #2BAB63; } + +pre.rust .comment { color: #8d8d8b; } +pre.rust .doccomment { color: #8ca375; } + +nav { + border-bottom-color: #4e4e4e; +} +nav.main .current { + border-top-color: #eee; + border-bottom-color: #eee; +} +nav.main .separator { + border-color: #eee; +} +a { + color: #ddd; +} + +.docblock a, .docblock-short a, .stability a { + color: #D2991D; +} + +a.test-arrow { + color: #dedede; +} + +.collapse-toggle { + color: #999; +} + +.search-input { + color: #111; + box-shadow: 0 0 0 1px #000, 0 0 0 2px transparent; + background-color: #f0f0f0; +} + +.search-input:focus { + border-color: #008dfd; +} + +.stab.unstable { background: #FFF5D6; border-color: #FFC600; color: #404040; } +.stab.deprecated { background: #F3DFFF; border-color: #7F0087; color: #404040; } +.stab.portability { background: #C4ECFF; border-color: #7BA5DB; color: #404040; } + +.module-item .stab { + color: #ddd; +} + +#help > div { + background: #4d4d4d; + border-color: #bfbfbf; +} + +#help dt { + border-color: #bfbfbf; + background: #fff; + color: black; +} + +.since { + color: grey; +} + +tr.result span.primitive::after { + color: #ddd; +} + +.line-numbers :target { background-color: transparent; } + +/* Code highlighting */ +pre.rust .kw { color: #ab8ac1; } +pre.rust .kw-2, pre.rust .prelude-ty { color: #769acb; } +pre.rust .number, pre.rust .string { color: #83a300; } +pre.rust .self, pre.rust .bool-val, pre.rust .prelude-val, +pre.rust .attribute, pre.rust .attribute .ident { color: #ee6868; } +pre.rust .macro, pre.rust .macro-nonterminal { color: #3E999F; } +pre.rust .lifetime { color: #d97f26; } +pre.rust .question-mark { + color: #ff9011; +} + +a.test-arrow { + background-color: rgba(78, 139, 202, 0.2); +} + +a.test-arrow:hover{ + background-color: #4e8bca; +} + +.toggle-label { + color: #999; +} + +:target > code { + background: #FDFFD3; +} + +pre.compile_fail { + border-left: 2px solid rgba(255,0,0,.6); +} + +pre.compile_fail:hover, .information:hover + pre.compile_fail { + border-left: 2px solid #f00; +} + +pre.ignore { + border-left: 2px solid rgba(255,142,0,.6); +} + +pre.ignore:hover, .information:hover + pre.ignore { + border-left: 2px solid #ff9200; +} + +.tooltip.compile_fail { + color: rgba(255,0,0,.6); +} + +.information > .compile_fail:hover { + color: #f00; +} + +.tooltip.ignore { + color: rgba(255,142,0,.6); +} + +.information > .ignore:hover { + color: rgba(255,142,0,1); +} + +.search-failed > a { + color: #0089ff; +} + +.tooltip .tooltiptext { + background-color: black; + color: #fff; +} + +.tooltip .tooltiptext::after { + border-color: transparent black transparent transparent; +} + +.important-traits .tooltip .tooltiptext { + background-color: white; + color: black; + border-color: black; +} + +#titles > div { + border-bottom-color: #ccc; +} + +#titles > div.selected { + border-bottom-color: #0078ee; +} + +#titles > div:hover { + border-bottom-color: #0089ff; +} + +#titles > div > div.count { + color: #888; +} + +.modal { + background-color: rgba(0,0,0,0.3); +} + +.modal-content { + background-color: #272727; + border-color: #999; +} + +.modal-content > .close { + background-color: #272727; + border-color: #999; +} + +.modal-content > .close:hover { + background-color: #ff1f1f; + color: white; +} + +.modal-content > .whiter { + background-color: #272727; +} + +.modal-content > .close:hover + .whiter { + background-color: #ff1f1f; +} + +@media (max-width: 700px) { + .sidebar-menu { + background-color: #505050; + border-bottom-color: #e0e0e0; + border-right-color: #e0e0e0; + } + + .sidebar-elems { + background-color: #505050; + border-right-color: #000; + } + + #sidebar-filler { + background-color: #505050; + border-bottom-color: #e0e0e0; + } +} + +kbd { + color: #444d56; + background-color: #fafbfc; + border-color: #d1d5da; + border-bottom-color: #c6cbd1; + box-shadow-color: #c6cbd1; +} + +#theme-picker { + border-color: #e0e0e0; + background: #f0f0f0; +} + +#theme-choices { + border-color: #e0e0e0; + background-color: #353535; +} + +#theme-choices > div { + border-top: #e0e0e0; +} + +#theme-choices > div:hover { + background-color: #444; +} + +@media (max-width: 700px) { + #theme-picker { + background: #353535; + } +} diff --git a/src/librustdoc/html/static/styles/main.css b/src/librustdoc/html/static/themes/main.css similarity index 96% rename from src/librustdoc/html/static/styles/main.css rename to src/librustdoc/html/static/themes/main.css index bd74056242442..84b21e7239fc1 100644 --- a/src/librustdoc/html/static/styles/main.css +++ b/src/librustdoc/html/static/themes/main.css @@ -351,3 +351,26 @@ kbd { border-bottom-color: #c6cbd1; box-shadow-color: #c6cbd1; } + +#theme-picker { + border-color: #e0e0e0; +} + +#theme-choices { + border-color: #ccc; + background-color: #fff; +} + +#theme-choices > div { + border-top: #e0e0e0; +} + +#theme-choices > div:hover { + background-color: #eee; +} + +@media (max-width: 700px) { + #theme-picker { + background: #fff; + } +} diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 2e2dba7681cc3..bf624c31d3d95 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -264,6 +264,11 @@ pub fn opts() -> Vec<RustcOptGroup> { o.optflag("", "deny-render-differences", "abort doc runs when markdown rendering \ differences are found") }), + unstable("themes", |o| { + o.optmulti("", "themes", + "additional themes which will be added to the generated docs", + "FILES") + }), ] } @@ -365,6 +370,15 @@ pub fn main_args(args: &[String]) -> isize { } } + let mut themes = Vec::new(); + for theme in matches.opt_strs("themes").iter().map(|s| PathBuf::from(&s)) { + if !theme.is_file() { + eprintln!("rustdoc: option --themes arguments must all be files"); + return 1; + } + themes.push(theme); + } + let external_html = match ExternalHtml::load( &matches.opt_strs("html-in-header"), &matches.opt_strs("html-before-content"), @@ -413,7 +427,8 @@ pub fn main_args(args: &[String]) -> isize { renderinfo, render_type, sort_modules_alphabetically, - deny_render_differences) + deny_render_differences, + themes) .expect("failed to generate documentation"); 0 }