From 0983438faa0431eb392be1d8ea9761fe4b1e90e2 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Thu, 7 Sep 2023 18:45:24 -0700 Subject: [PATCH] rustdoc: allow resizing the sidebar --- src/librustdoc/html/markdown.rs | 1 + src/librustdoc/html/static/css/noscript.css | 6 +- src/librustdoc/html/static/css/rustdoc.css | 131 +++++++++++++++++- src/librustdoc/html/static/images/sidebar.svg | 15 ++ src/librustdoc/html/static/js/main.js | 116 +++++++++++++++- src/librustdoc/html/static/js/settings.js | 19 +++ src/librustdoc/html/static/js/src-script.js | 22 ++- src/librustdoc/html/static/js/storage.js | 25 ++++ src/librustdoc/html/static_files.rs | 1 + src/librustdoc/html/templates/page.html | 13 +- tests/rustdoc-gui/hide-mobile-topbar.goml | 20 +++ tests/rustdoc-gui/links-color.goml | 4 +- tests/rustdoc-gui/sidebar-links-color.goml | 4 +- tests/rustdoc-gui/sidebar-resize-setting.goml | 23 +++ tests/rustdoc-gui/sidebar-resize.goml | 28 ++++ tests/rustdoc-gui/sidebar-source-code.goml | 3 +- tests/rustdoc-gui/sidebar.goml | 4 +- .../src/theme_css/custom-theme.css | 2 + 18 files changed, 409 insertions(+), 28 deletions(-) create mode 100644 src/librustdoc/html/static/images/sidebar.svg create mode 100644 tests/rustdoc-gui/hide-mobile-topbar.goml create mode 100644 tests/rustdoc-gui/sidebar-resize-setting.goml create mode 100644 tests/rustdoc-gui/sidebar-resize.goml diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index d24e6e5faf5..44a9ca76aba 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -2000,6 +2000,7 @@ fn init_id_map() -> FxHashMap, usize> { map.insert("themeStyle".into(), 1); map.insert("settings-menu".into(), 1); map.insert("help-button".into(), 1); + map.insert("sidebar-button".into(), 1); map.insert("main-content".into(), 1); map.insert("toggle-all-docs".into(), 1); map.insert("all-types".into(), 1); diff --git a/src/librustdoc/html/static/css/noscript.css b/src/librustdoc/html/static/css/noscript.css index fe0cf6dc8cc..390e812772a 100644 --- a/src/librustdoc/html/static/css/noscript.css +++ b/src/librustdoc/html/static/css/noscript.css @@ -9,7 +9,7 @@ rules. margin-left: 0 !important; } -#copy-path { +#copy-path, #sidebar-button, .sidebar-resizer { /* It requires JS to work so no need to display it in this case. */ display: none; } @@ -132,6 +132,8 @@ nav.sub { --scrape-example-help-hover-color: #000; --scrape-example-code-wrapper-background-start: rgba(255, 255, 255, 1); --scrape-example-code-wrapper-background-end: rgba(255, 255, 255, 0); + --sidebar-resizer-hover: hsl(207, 90%, 66%); + --sidebar-resizer-active: hsl(207, 90%, 54%); } /* End theme: light */ @@ -238,6 +240,8 @@ nav.sub { --scrape-example-help-hover-color: #fff; --scrape-example-code-wrapper-background-start: rgba(53, 53, 53, 1); --scrape-example-code-wrapper-background-end: rgba(53, 53, 53, 0); + --sidebar-resizer-hover: hsl(207, 30%, 54%); + --sidebar-resizer-active: hsl(207, 90%, 54%); } /* End theme: dark */ } diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index e2b4cc50dd5..85a4ff7a621 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -383,7 +383,7 @@ img { .sidebar { font-size: 0.875rem; - flex: 0 0 200px; + flex: 0 0 var(--desktop-sidebar-width, 200px); overflow-y: scroll; overscroll-behavior: contain; position: sticky; @@ -401,6 +401,87 @@ img { z-index: 1; } +.hide-sidebar .sidebar, +.hide-sidebar .sidebar-resizer { + display: none; +} + +.sidebar-resizer { + touch-action: none; + width: 9px; + cursor: col-resize; + z-index: 10; + position: absolute; + height: 100%; + /* make sure there's a 1px gap between the scrollbar and resize handle */ + left: calc(var(--desktop-sidebar-width, 200px) + 1px); +} + +.rustdoc.src .sidebar-resizer { + /* when closed, place resizer glow on top of the normal src sidebar border (no need to worry + about sidebar) */ + left: 49px; +} + +.src-sidebar-expanded .rustdoc.src .sidebar-resizer { + /* for src sidebar, gap is already provided by 1px border on sidebar itself, so place resizer + to right of it */ + left: var(--src-sidebar-width, 300px); +} + +.sidebar-resizing { + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.sidebar-resizing * { + cursor: col-resize !important; +} + +.sidebar-resizer:hover, +.sidebar-resizer:active, +.sidebar-resizer:focus, +.sidebar-resizer.active { + width: 10px; + margin: 0; + /* when active or hovered, place resizer glow on top of the sidebar (right next to, or even + on top of, the scrollbar) */ + left: var(--desktop-sidebar-width, 200px); + border-left: solid 1px var(--sidebar-resizer-hover); +} + +.src-sidebar-expanded .rustdoc.src .sidebar-resizer:hover, +.src-sidebar-expanded .rustdoc.src .sidebar-resizer:active, +.src-sidebar-expanded .rustdoc.src .sidebar-resizer:focus, +.src-sidebar-expanded .rustdoc.src .sidebar-resizer.active { + /* when active or hovered, place resizer glow on top of the normal src sidebar border */ + left: calc(var(--src-sidebar-width, 300px) - 1px); +} + +@media (pointer: coarse) { + .sidebar-resizer { + /* too easy to hit the resizer while trying to hit the [-] toggle */ + display: none !important; + } +} + +.sidebar-resizer.active { + /* make the resize tool bigger when actually resizing, to avoid :hover styles on other stuff + while resizing */ + padding: 0 140px; + width: 2px; + margin-left: -140px; + border-left: none; +} +.sidebar-resizer.active:before { + border-left: solid 2px var(--sidebar-resizer-active); + display: block; + height: 100%; + content: ""; +} + .sidebar, .mobile-topbar, .sidebar-menu-toggle, #src-sidebar-toggle, #src-sidebar { background-color: var(--sidebar-background-color); @@ -416,7 +497,7 @@ img { .src-sidebar-expanded .src .sidebar { overflow-y: auto; - flex-basis: 300px; + flex-basis: var(--src-sidebar-width, 300px); } .src-sidebar-expanded .src .sidebar > *:not(#src-sidebar-toggle) { @@ -477,6 +558,7 @@ ul.block, .block li { display: block; padding: 0.25rem; /* 4px */ margin-left: -0.25rem; + margin-right: 0.25rem; } .sidebar h2 { @@ -778,7 +860,7 @@ h2.small-section-header > .anchor { text-decoration: underline; } -.crate.block a.current { font-weight: 500; } +.crate.block li.current a { font-weight: 500; } /* In most contexts we use `overflow-wrap: anywhere` to ensure that we can wrap as much as needed on mobile (see @@ -1477,7 +1559,20 @@ a.tooltip:hover::after { margin-left: 4px; display: flex; } -#settings-menu > a, #help-button > a { +#sidebar-button { + display: none; +} +.hide-sidebar #sidebar-button { + display: flex; + margin-right: 4px; + position: fixed; + left: 6px; + height: 34px; + width: 34px; + background-color: var(--main-background-color); + z-index: 1; +} +#settings-menu > a, #help-button > a, #sidebar-button > a { display: flex; align-items: center; justify-content: center; @@ -1492,7 +1587,8 @@ a.tooltip:hover::after { } #settings-menu > a:hover, #settings-menu > a:focus, -#help-button > a:hover, #help-button > a:focus { +#help-button > a:hover, #help-button > a:focus, +#sidebar-button > a:hover, #sidebar-button > a:focus { border-color: var(--settings-button-border-focus); } @@ -1721,6 +1817,10 @@ in src-script.js scroll-margin-top: 45px; } + .hide-sidebar #sidebar-button { + position: static; + } + .rustdoc { /* Sidebar should overlay main content, rather than pushing main content to the right. Turn off `display: flex` on the body element. */ @@ -1749,7 +1849,8 @@ in src-script.js /* Hide the logo and item name from the sidebar. Those are displayed in the mobile-topbar instead. */ .sidebar .logo-container, - .sidebar .location { + .sidebar .location, + .sidebar-resizer { display: none; } @@ -1817,6 +1918,10 @@ in src-script.js top: 0; } + .hide-sidebar .mobile-topbar { + display: none; + } + .sidebar-menu-toggle { width: 45px; /* Rare exception to specifying font sizes in rem. Since this is acting @@ -1826,6 +1931,10 @@ in src-script.js color: var(--main-color); } + .hide-sidebar .sidebar-menu-toggle { + display: none; + } + .sidebar-elems { margin-top: 1em; } @@ -2273,6 +2382,8 @@ in src-script.js --scrape-example-help-hover-color: #000; --scrape-example-code-wrapper-background-start: rgba(255, 255, 255, 1); --scrape-example-code-wrapper-background-end: rgba(255, 255, 255, 0); + --sidebar-resizer-hover: hsl(207, 90%, 66%); + --sidebar-resizer-active: hsl(207, 90%, 54%); } /* End theme: light */ @@ -2378,6 +2489,8 @@ in src-script.js --scrape-example-help-hover-color: #fff; --scrape-example-code-wrapper-background-start: rgba(53, 53, 53, 1); --scrape-example-code-wrapper-background-end: rgba(53, 53, 53, 0); + --sidebar-resizer-hover: hsl(207, 30%, 54%); + --sidebar-resizer-active: hsl(207, 90%, 54%); } /* End theme: dark */ @@ -2487,6 +2600,8 @@ Original by Dempfi (https://github.com/dempfi/ayu) --scrape-example-help-hover-color: #fff; --scrape-example-code-wrapper-background-start: rgba(15, 20, 25, 1); --scrape-example-code-wrapper-background-end: rgba(15, 20, 25, 0); + --sidebar-resizer-hover: hsl(34, 50%, 33%); + --sidebar-resizer-active: hsl(34, 100%, 66%); } :root[data-theme="ayu"] h1, @@ -2518,6 +2633,7 @@ Original by Dempfi (https://github.com/dempfi/ayu) } :root[data-theme="ayu"] .sidebar .current, +:root[data-theme="ayu"] .sidebar .current a, :root[data-theme="ayu"] .sidebar a:hover, :root[data-theme="ayu"] #src-sidebar div.files > a:hover, :root[data-theme="ayu"] details.dir-entry summary:hover, @@ -2568,7 +2684,8 @@ Original by Dempfi (https://github.com/dempfi/ayu) border-bottom: 1px solid rgba(242, 151, 24, 0.3); } -:root[data-theme="ayu"] #settings-menu > a img { +:root[data-theme="ayu"] #settings-menu > a img, +:root[data-theme="ayu"] #sidebar-button > a img { filter: invert(100); } /* End theme: ayu */ diff --git a/src/librustdoc/html/static/images/sidebar.svg b/src/librustdoc/html/static/images/sidebar.svg new file mode 100644 index 00000000000..1d4f99f69cd --- /dev/null +++ b/src/librustdoc/html/static/images/sidebar.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 2e9897ef82b..ac2a5e513d7 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -1,5 +1,5 @@ // Local js definitions: -/* global addClass, getSettingValue, hasClass, searchState */ +/* global addClass, getSettingValue, hasClass, searchState, updateLocalStorage */ /* global onEach, onEachLazy, removeClass, getVar */ "use strict"; @@ -505,7 +505,7 @@ function preLoadCss(cssUrl) { } const link = document.createElement("a"); link.href = path; - if (link.href === current_page) { + if (path === current_page) { link.className = "current"; } link.textContent = name; @@ -656,12 +656,12 @@ function preLoadCss(cssUrl) { for (const crate of window.ALL_CRATES) { const link = document.createElement("a"); link.href = window.rootPath + crate + "/index.html"; - if (window.rootPath !== "./" && crate === window.currentCrate) { - link.className = "current"; - } link.textContent = crate; const li = document.createElement("li"); + if (window.rootPath !== "./" && crate === window.currentCrate) { + li.className = "current"; + } li.appendChild(link); ul.appendChild(li); } @@ -1273,6 +1273,112 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ searchState.setup(); }()); +(function() { + const sidebarButton = document.getElementById("sidebar-button"); + if (sidebarButton) { + sidebarButton.addEventListener("click", e => { + removeClass(document.documentElement, "hide-sidebar"); + updateLocalStorage("hide-sidebar", "false"); + e.preventDefault(); + }); + } + let currentPointerId = null; + const resizer = document.getElementsByClassName("sidebar-resizer")[0]; + const sidebar = document.getElementsByClassName("sidebar")[0]; + if (!resizer || !sidebar) { + return; + } + const isSrcPage = hasClass(document.body, "src"); + function hideSidebar() { + if (isSrcPage) { + window.rustdocCloseSourceSidebar(); + updateLocalStorage("src-sidebar-width", null); + document.documentElement.style.removeProperty("--src-sidebar-width"); + } else { + addClass(document.documentElement, "hide-sidebar"); + updateLocalStorage("hide-sidebar", "true"); + updateLocalStorage("desktop-sidebar-width", null); + document.documentElement.style.removeProperty("--desktop-sidebar-width"); + } + } + function showSidebar() { + if (isSrcPage) { + window.rustdocShowSourceSidebar(); + } else { + removeClass(document.documentElement, "hide-sidebar"); + updateLocalStorage("hide-sidebar", "false"); + } + } + function changeSidebarSize(size) { + if (isSrcPage) { + updateLocalStorage("src-sidebar-width", size); + document.documentElement.style.setProperty("--src-sidebar-width", size + "px"); + } else { + updateLocalStorage("desktop-sidebar-width", size); + document.documentElement.style.setProperty("--desktop-sidebar-width", size + "px"); + } + } + function isSidebarHidden() { + return isSrcPage ? + !hasClass(document.documentElement, "src-sidebar-expanded") : + hasClass(document.documentElement, "hide-sidebar"); + } + function resize(e) { + if (currentPointerId === null || currentPointerId !== e.pointerId) { + return; + } + e.preventDefault(); + const pos = e.clientX - sidebar.offsetLeft - 3; + if (pos < 50) { + hideSidebar(); + } else if (pos >= 100) { + // 100 is the size of the logo + // don't let the sidebar get smaller than that, or it'll get squished + if (isSidebarHidden()) { + showSidebar(); + } + // don't let the sidebar get wider than 500 + changeSidebarSize(Math.min(pos, window.innerWidth - 100, 500)); + } + } + function stopResize(e) { + if (currentPointerId === null) { + return; + } + e.preventDefault(); + removeClass(resizer, "active"); + window.removeEventListener("pointermove", resize, false); + window.removeEventListener("pointerup", stopResize, false); + removeClass(document.documentElement, "sidebar-resizing"); + if (resizer.releasePointerCapture) { + resizer.releasePointerCapture(currentPointerId); + currentPointerId = null; + } + } + function initResize(e) { + if (currentPointerId !== null || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { + return; + } + if (resizer.setPointerCapture) { + resizer.setPointerCapture(e.pointerId); + if (!resizer.hasPointerCapture(e.pointerId)) { + // unable to capture pointer; something else has it + // on iOS, this usually means you long-clicked a link instead + resizer.releasePointerCapture(e.pointerId); + return; + } + currentPointerId = e.pointerId; + } + e.preventDefault(); + window.addEventListener("pointermove", resize, false); + window.addEventListener("pointercancel", stopResize, false); + window.addEventListener("pointerup", stopResize, false); + addClass(resizer, "active"); + addClass(document.documentElement, "sidebar-resizing"); + } + resizer.addEventListener("pointerdown", initResize, false); +}()); + (function() { let reset_button_timeout = null; diff --git a/src/librustdoc/html/static/js/settings.js b/src/librustdoc/html/static/js/settings.js index 63947789c54..a9333c6429d 100644 --- a/src/librustdoc/html/static/js/settings.js +++ b/src/librustdoc/html/static/js/settings.js @@ -29,6 +29,13 @@ window.rustdoc_remove_line_numbers_from_examples(); } break; + case "hide-sidebar": + if (value === true) { + addClass(document.documentElement, "hide-sidebar"); + } else { + removeClass(document.documentElement, "hide-sidebar"); + } + break; } } @@ -186,6 +193,11 @@ "js_name": "line-numbers", "default": false, }, + { + "name": "Hide persistent navigation bar", + "js_name": "hide-sidebar", + "default": false, + }, { "name": "Disable keyboard shortcuts", "js_name": "disable-shortcuts", @@ -216,6 +228,13 @@ function displaySettings() { settingsMenu.style.display = ""; + onEachLazy(settingsMenu.querySelectorAll("input[type='checkbox']"), el => { + const val = getSettingValue(el.id); + const checked = val === "true"; + if (checked !== el.checked && val !== null) { + el.checked = checked; + } + }); } function settingsBlurHandler(event) { diff --git a/src/librustdoc/html/static/js/src-script.js b/src/librustdoc/html/static/js/src-script.js index 679c2341f02..4caf404f928 100644 --- a/src/librustdoc/html/static/js/src-script.js +++ b/src/librustdoc/html/static/js/src-script.js @@ -71,16 +71,26 @@ function createDirEntry(elem, parent, fullPath, hasFoundFile) { return hasFoundFile; } +window.rustdocCloseSourceSidebar = () => { + const toggleLabel = document.querySelector("#src-sidebar-toggle button"); + removeClass(document.documentElement, "src-sidebar-expanded"); + toggleLabel.innerText = ">"; + updateLocalStorage("source-sidebar-show", "false"); +}; + +window.rustdocShowSourceSidebar = () => { + const toggleLabel = document.querySelector("#src-sidebar-toggle button"); + addClass(document.documentElement, "src-sidebar-expanded"); + toggleLabel.innerText = "<"; + updateLocalStorage("source-sidebar-show", "true"); +}; + function toggleSidebar() { const child = this.parentNode.children[0]; if (child.innerText === ">") { - addClass(document.documentElement, "src-sidebar-expanded"); - child.innerText = "<"; - updateLocalStorage("source-sidebar-show", "true"); + window.rustdocShowSourceSidebar(); } else { - removeClass(document.documentElement, "src-sidebar-expanded"); - child.innerText = ">"; - updateLocalStorage("source-sidebar-show", "false"); + window.rustdocCloseSourceSidebar(); } } diff --git a/src/librustdoc/html/static/js/storage.js b/src/librustdoc/html/static/js/storage.js index c69641092ab..20220f2e69a 100644 --- a/src/librustdoc/html/static/js/storage.js +++ b/src/librustdoc/html/static/js/storage.js @@ -202,6 +202,30 @@ if (getSettingValue("source-sidebar-show") === "true") { addClass(document.documentElement, "src-sidebar-expanded"); } +if (getSettingValue("hide-sidebar") === "true") { + // At this point in page load, `document.body` is not available yet. + // Set a class on the `` element instead. + addClass(document.documentElement, "hide-sidebar"); +} + +function updateSidebarWidth() { + const desktopSidebarWidth = getSettingValue("desktop-sidebar-width"); + if (desktopSidebarWidth && desktopSidebarWidth !== "null") { + document.documentElement.style.setProperty( + "--desktop-sidebar-width", + desktopSidebarWidth + "px" + ); + } + const srcSidebarWidth = getSettingValue("src-sidebar-width"); + if (srcSidebarWidth && srcSidebarWidth !== "null") { + document.documentElement.style.setProperty( + "--src-sidebar-width", + srcSidebarWidth + "px" + ); + } +} +updateSidebarWidth(); + // If we navigate away (for example to a settings page), and then use the back or // forward button to get back to a page, the theme may have changed in the meantime. // But scripts may not be re-loaded in such a case due to the bfcache @@ -214,5 +238,6 @@ if (getSettingValue("source-sidebar-show") === "true") { window.addEventListener("pageshow", ev => { if (ev.persisted) { setTimeout(updateTheme, 0); + setTimeout(updateSidebarWidth, 0); } }); diff --git a/src/librustdoc/html/static_files.rs b/src/librustdoc/html/static_files.rs index ca9a78f51b3..080958b1cbc 100644 --- a/src/librustdoc/html/static_files.rs +++ b/src/librustdoc/html/static_files.rs @@ -100,6 +100,7 @@ pub(crate) fn for_each(f: impl Fn(&StaticFile) -> Result<(), E>) -> Result<() storage_js => "static/js/storage.js", scrape_examples_js => "static/js/scrape-examples.js", wheel_svg => "static/images/wheel.svg", + sidebar_svg => "static/images/sidebar.svg", clipboard_svg => "static/images/clipboard.svg", copyright => "static/COPYRIGHT.txt", license_apache => "static/LICENSE-APACHE.txt", diff --git a/src/librustdoc/html/templates/page.html b/src/librustdoc/html/templates/page.html index ebf817673bf..76cdb5eb38f 100644 --- a/src/librustdoc/html/templates/page.html +++ b/src/librustdoc/html/templates/page.html @@ -115,6 +115,7 @@ {% endif %} {{ sidebar|safe }} {# #} +
{# #} {% if page.css_class != "src" %}
{% endif %}