From 0983438faa0431eb392be1d8ea9761fe4b1e90e2 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Thu, 7 Sep 2023 18:45:24 -0700 Subject: rustdoc: allow resizing the sidebar --- 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 ++++++ 4 files changed, 171 insertions(+), 11 deletions(-) (limited to 'src/librustdoc/html/static/js') 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); } }); -- cgit 1.4.1-3-g733a5 From 210c88fc7ae55df147b45b9e75f95874c1a589ef Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 26 Sep 2023 16:31:33 -0700 Subject: rustdoc: clean up main.js and src-script.js * Run the querySelector for the toggleLabel only once, and store the result. * Use querySelector to find the resizer and sidebar. * Add comments to main.js sections. --- src/librustdoc/html/static/js/main.js | 6 ++++-- src/librustdoc/html/static/js/src-script.js | 13 +++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) (limited to 'src/librustdoc/html/static/js') diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index ac2a5e513d7..851ee795d4d 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -1273,6 +1273,7 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ searchState.setup(); }()); +// This section handles sidebar resizing (function() { const sidebarButton = document.getElementById("sidebar-button"); if (sidebarButton) { @@ -1283,8 +1284,8 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ }); } let currentPointerId = null; - const resizer = document.getElementsByClassName("sidebar-resizer")[0]; - const sidebar = document.getElementsByClassName("sidebar")[0]; + const resizer = document.querySelector(".sidebar-resizer"); + const sidebar = document.querySelector(".sidebar"); if (!resizer || !sidebar) { return; } @@ -1379,6 +1380,7 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ resizer.addEventListener("pointerdown", initResize, false); }()); +// This section handles the copy button that appears next to the path breadcrumbs (function() { let reset_button_timeout = null; diff --git a/src/librustdoc/html/static/js/src-script.js b/src/librustdoc/html/static/js/src-script.js index 4caf404f928..c0ec0010e4c 100644 --- a/src/librustdoc/html/static/js/src-script.js +++ b/src/librustdoc/html/static/js/src-script.js @@ -71,17 +71,22 @@ function createDirEntry(elem, parent, fullPath, hasFoundFile) { return hasFoundFile; } +let toggleLabel; + +function getToggleLabel() { + toggleLabel = toggleLabel || document.querySelector("#src-sidebar-toggle button"); + return toggleLabel; +} + window.rustdocCloseSourceSidebar = () => { - const toggleLabel = document.querySelector("#src-sidebar-toggle button"); removeClass(document.documentElement, "src-sidebar-expanded"); - toggleLabel.innerText = ">"; + getToggleLabel().innerText = ">"; updateLocalStorage("source-sidebar-show", "false"); }; window.rustdocShowSourceSidebar = () => { - const toggleLabel = document.querySelector("#src-sidebar-toggle button"); addClass(document.documentElement, "src-sidebar-expanded"); - toggleLabel.innerText = "<"; + getToggleLabel().innerText = "<"; updateLocalStorage("source-sidebar-show", "true"); }; -- cgit 1.4.1-3-g733a5 From 273a302ac8a5fed73bb17988ed571339dc6217ad Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Wed, 27 Sep 2023 08:59:32 -0700 Subject: rustdoc: enforce BODY_MIN constraint on sidebar resize --- src/librustdoc/html/static/css/rustdoc.css | 19 +++-- src/librustdoc/html/static/js/main.js | 113 +++++++++++++++++++++++++-- src/librustdoc/html/static/js/storage.js | 7 +- tests/rustdoc-gui/sidebar-resize-window.goml | 33 ++++++++ 4 files changed, 155 insertions(+), 17 deletions(-) create mode 100644 tests/rustdoc-gui/sidebar-resize-window.goml (limited to 'src/librustdoc/html/static/js') diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 85a4ff7a621..0bc687da6bf 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -9,6 +9,11 @@ :root { --nav-sub-mobile-padding: 8px; --search-typename-width: 6.75rem; + /* DEFAULT_SIDEBAR_WIDTH + see main.js for information on these values + and on the RUSTDOC_MOBILE_BREAKPOINT */ + --desktop-sidebar-width: 200px; + --src-sidebar-width: 300px; } /* See FiraSans-LICENSE.txt for the Fira Sans license. */ @@ -383,7 +388,7 @@ img { .sidebar { font-size: 0.875rem; - flex: 0 0 var(--desktop-sidebar-width, 200px); + flex: 0 0 var(--desktop-sidebar-width); overflow-y: scroll; overscroll-behavior: contain; position: sticky; @@ -414,7 +419,7 @@ img { 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); + left: calc(var(--desktop-sidebar-width) + 1px); } .rustdoc.src .sidebar-resizer { @@ -426,7 +431,7 @@ img { .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); + left: var(--src-sidebar-width); } .sidebar-resizing { @@ -448,7 +453,7 @@ img { 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); + left: var(--desktop-sidebar-width); border-left: solid 1px var(--sidebar-resizer-hover); } @@ -457,7 +462,7 @@ img { .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); + left: calc(var(--src-sidebar-width) - 1px); } @media (pointer: coarse) { @@ -497,7 +502,7 @@ img { .src-sidebar-expanded .src .sidebar { overflow-y: auto; - flex-basis: var(--src-sidebar-width, 300px); + flex-basis: var(--src-sidebar-width); } .src-sidebar-expanded .src .sidebar > *:not(#src-sidebar-toggle) { @@ -1806,7 +1811,7 @@ However, it's not needed with smaller screen width because the doc/code block is /* WARNING: RUSTDOC_MOBILE_BREAKPOINT MEDIA QUERY If you update this line, then you also need to update the line with the same warning -in src-script.js +in src-script.js and main.js */ @media (max-width: 700px) { /* When linking to an item with an `id` (for instance, by clicking a link in the sidebar, diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 851ee795d4d..e1d674e7d04 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -1273,8 +1273,49 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ searchState.setup(); }()); -// This section handles sidebar resizing +// Hide, show, and resize the sidebar +// +// The body class and CSS variable are initially set up in storage.js, +// but in this file, we implement: +// +// * the show sidebar button, which appears if the sidebar is hidden +// and, by clicking on it, will bring it back +// * the sidebar resize handle, which appears only on large viewports +// with a [fine precision pointer] to allow the user to change +// the size of the sidebar +// +// [fine precision pointer]: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/pointer (function() { + // 100 is the size of the logo + // don't let the sidebar get smaller than that, or it'll get squished + const SIDEBAR_MIN = 100; + // Don't let the sidebar get bigger than this + const SIDEBAR_MAX = 500; + // Don't let the body (including the gutter) get smaller than this + // + // WARNING: RUSTDOC_MOBILE_BREAKPOINT MEDIA QUERY + // Acceptable values for BODY_MIN are constrained by the mobile breakpoint + // (which is the minimum size of the whole page where the sidebar exists) + // and the default sidebar width: + // + // BODY_MIN <= RUSTDOC_MOBILE_BREAKPOINT - DEFAULT_SIDEBAR_WIDTH + // + // At the time of this writing, the DEFAULT_SIDEBAR_WIDTH on src pages is + // 300px, and the RUSTDOC_MOBILE_BREAKPOINT is 700px, so BODY_MIN must be + // at most 400px. Otherwise, it would start out at the default size, then + // grabbing the resize handle would suddenly cause it to jank to + // its contraint-generated maximum. + const BODY_MIN = 400; + // At half-way past the minimum size, vanish the sidebar entirely + const SIDEBAR_VANISH_THRESHOLD = SIDEBAR_MIN / 2; + + // Toolbar button to show the sidebar. + // + // On small, "mobile-sized" viewports, it's not persistent and it + // can only be activated by going into Settings and hiding the nav bar. + // On larger, "desktop-sized" viewports (though that includes many + // tablets), it's fixed-position, appears in the left side margin, + // and it can be activated by resizing the sidebar into nothing. const sidebarButton = document.getElementById("sidebar-button"); if (sidebarButton) { sidebarButton.addEventListener("click", e => { @@ -1283,13 +1324,38 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ e.preventDefault(); }); } + + // Pointer capture. + // + // Resizing is a single-pointer gesture. Any secondary pointer is ignored let currentPointerId = null; + + // "Desired" sidebar size. + // + // This is stashed here for window resizing. If the sidebar gets + // shrunk to maintain BODY_MIN, and then the user grows the window again, + // it gets the sidebar to restore its size. + let desiredSidebarSize = null; + + // If this page has no sidebar at all, bail out. const resizer = document.querySelector(".sidebar-resizer"); const sidebar = document.querySelector(".sidebar"); if (!resizer || !sidebar) { return; } + + // src page and docs page use different variables, because the contents of + // the sidebar are so different that it's reasonable to thing the user + // would want them to have different sizes const isSrcPage = hasClass(document.body, "src"); + + // Call this function to hide the sidebar when using the resize handle + // + // This function also nulls out the sidebar width CSS variable and setting, + // causing it to return to its default. This does not happen if you do it + // from settings.js, which uses a separate function. It's done here because + // the minimum sidebar size is rather uncomfortable, and it must pass + // through that size when using the shrink-to-nothing gesture. function hideSidebar() { if (isSrcPage) { window.rustdocCloseSourceSidebar(); @@ -1302,6 +1368,13 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ document.documentElement.style.removeProperty("--desktop-sidebar-width"); } } + + // Call this function to show the sidebar from the resize handle. + // On docs pages, this can only happen if the user has grabbed the resize + // handle, shrunk the sidebar down to nothing, and then pulls back into + // the visible range without releasing it. You can, however, grab the + // resize handle on a source page with the sidebar closed, because it + // remains visible all the time on there. function showSidebar() { if (isSrcPage) { window.rustdocShowSourceSidebar(); @@ -1310,6 +1383,9 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ updateLocalStorage("hide-sidebar", "false"); } } + + // Call this to set the correct CSS variable and setting. + // This function doesn't enforce size constraints. Do that before calling it! function changeSidebarSize(size) { if (isSrcPage) { updateLocalStorage("src-sidebar-width", size); @@ -1319,34 +1395,54 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ document.documentElement.style.setProperty("--desktop-sidebar-width", size + "px"); } } + + // Check if the sidebar is hidden. Since src pages and doc pages have + // different settings, this function has to check that. function isSidebarHidden() { return isSrcPage ? !hasClass(document.documentElement, "src-sidebar-expanded") : hasClass(document.documentElement, "hide-sidebar"); } + + // Respond to the resize handle event. + // This function enforces size constraints, and implements the + // shrink-to-nothing gesture based on thresholds defined above. function resize(e) { if (currentPointerId === null || currentPointerId !== e.pointerId) { return; } e.preventDefault(); const pos = e.clientX - sidebar.offsetLeft - 3; - if (pos < 50) { + if (pos < SIDEBAR_VANISH_THRESHOLD) { 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 + } else if (pos >= SIDEBAR_MIN) { if (isSidebarHidden()) { showSidebar(); } - // don't let the sidebar get wider than 500 - changeSidebarSize(Math.min(pos, window.innerWidth - 100, 500)); + // don't let the sidebar get wider than SIDEBAR_MAX, or the body narrower + // than BODY_MIN + const constrainedPos = Math.min(pos, window.innerWidth - BODY_MIN, SIDEBAR_MAX); + changeSidebarSize(constrainedPos); + desiredSidebarSize = constrainedPos; } } + // Respond to the window resize event. + window.addEventListener("resize", () => { + stopResize(); + if (desiredSidebarSize >= (window.innerWidth - BODY_MIN)) { + changeSidebarSize(window.innerWidth - BODY_MIN); + } else if (desiredSidebarSize !== null && desiredSidebarSize > SIDEBAR_MIN) { + changeSidebarSize(desiredSidebarSize); + } + }); function stopResize(e) { if (currentPointerId === null) { return; } - e.preventDefault(); + if (e) { + e.preventDefault(); + } + desiredSidebarSize = sidebar.getBoundingClientRect().width; removeClass(resizer, "active"); window.removeEventListener("pointermove", resize, false); window.removeEventListener("pointerup", stopResize, false); @@ -1376,6 +1472,7 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ window.addEventListener("pointerup", stopResize, false); addClass(resizer, "active"); addClass(document.documentElement, "sidebar-resizing"); + desiredSidebarSize = null; } resizer.addEventListener("pointerdown", initResize, false); }()); diff --git a/src/librustdoc/html/static/js/storage.js b/src/librustdoc/html/static/js/storage.js index 20220f2e69a..18a127fbbd1 100644 --- a/src/librustdoc/html/static/js/storage.js +++ b/src/librustdoc/html/static/js/storage.js @@ -196,18 +196,21 @@ if (getSettingValue("use-system-theme") !== "false" && window.matchMedia) { updateTheme(); +// Hide, show, and resize the sidebar at page load time +// +// This needs to be done here because this JS is render-blocking, +// so that the sidebar doesn't "jump" after appearing on screen. +// The user interaction to change this is set up in main.js. if (getSettingValue("source-sidebar-show") === "true") { // At this point in page load, `document.body` is not available yet. // Set a class on the `` element instead. 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") { diff --git a/tests/rustdoc-gui/sidebar-resize-window.goml b/tests/rustdoc-gui/sidebar-resize-window.goml new file mode 100644 index 00000000000..04321acc105 --- /dev/null +++ b/tests/rustdoc-gui/sidebar-resize-window.goml @@ -0,0 +1,33 @@ +// Checks sidebar resizing +go-to: "file://" + |DOC_PATH| + "/test_docs/index.html" +set-window-size: (1280, 600) +wait-for-property: (".sidebar", {"clientWidth": 200}, [NEAR]) + +// resize past maximum (don't grow past 500) +drag-and-drop: ((205, 100), (600, 100)) +wait-for-property: (".sidebar", {"clientWidth": 500}, [NEAR]) + +// make the window small enough that the sidebar has to shrink +set-window-size: (750, 600) +wait-for-property: (".sidebar", {"clientWidth": 350}, [NEAR]) + +// grow the window again to make the sidebar bigger +set-window-size: (1280, 600) +wait-for-property: (".sidebar", {"clientWidth": 500}, [NEAR]) + +// make the window small enough that the sidebar has to shrink +set-window-size: (750, 600) +wait-for-property: (".sidebar", {"clientWidth": 350}, [NEAR]) + +// grow the window again to make the sidebar bigger +set-window-size: (1280, 600) +wait-for-property: (".sidebar", {"clientWidth": 500}, [NEAR]) + +// shrink back down again, then reload the page +// the "desired size" is a bit of remembered implicit state, +// and rustdoc tries to minimize things like this +set-window-size: (800, 600) +wait-for-property: (".sidebar", {"clientWidth": 400}, [NEAR]) +reload: +set-window-size: (1280, 600) +wait-for-property: (".sidebar", {"clientWidth": 400}, [NEAR]) -- cgit 1.4.1-3-g733a5 From 77fa09d9a1ddc7f99b0cda10c26688fe6f2b5519 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sat, 7 Oct 2023 11:10:36 -0700 Subject: rustdoc: avoid whole page layout on each frame This makes two changes, based on experimenting with different browsers: - It debounces resizing the body text. This improves behavior on huge pages like struct.Vec.html, because it doesn't have to do layout. - It does the sidebar width updates directly on the sidebar instead of doing it on the `` element. Doing it on `` causes it to recalculate CSS for the entire document, also causing layout jank. --- src/librustdoc/html/static/css/rustdoc.css | 18 +++++++--- src/librustdoc/html/static/js/main.js | 57 +++++++++++++++++++++++++++--- src/librustdoc/html/static_files.rs | 1 - 3 files changed, 67 insertions(+), 9 deletions(-) (limited to 'src/librustdoc/html/static/js') diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index d978d5bcb75..c66425dfa9b 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -389,12 +389,14 @@ img { .sidebar { font-size: 0.875rem; flex: 0 0 var(--desktop-sidebar-width); + width: var(--desktop-sidebar-width); overflow-y: scroll; overscroll-behavior: contain; position: sticky; height: 100vh; top: 0; left: 0; + z-index: 100; } .rustdoc.src .sidebar { @@ -403,7 +405,6 @@ img { overflow-x: hidden; /* The sidebar is by default hidden */ overflow-y: hidden; - z-index: 1; } .hide-sidebar .sidebar, @@ -415,8 +416,8 @@ img { touch-action: none; width: 9px; cursor: col-resize; - z-index: 10; - position: absolute; + z-index: 200; + position: fixed; height: 100%; /* make sure there's a 1px gap between the scrollbar and resize handle */ left: calc(var(--desktop-sidebar-width) + 1px); @@ -445,6 +446,14 @@ img { cursor: col-resize !important; } +.sidebar-resizing .sidebar { + position: fixed; + z-index: 100; +} +.sidebar-resizing > body { + padding-left: var(--resizing-sidebar-width); +} + .sidebar-resizer:hover, .sidebar-resizer:active, .sidebar-resizer:focus, @@ -474,7 +483,7 @@ img { .sidebar-resizer.active { /* make the resize tool bigger when actually resizing, to avoid :hover styles on other stuff - while resizing */ + while resizing */ padding: 0 140px; width: 2px; margin-left: -140px; @@ -503,6 +512,7 @@ img { .src-sidebar-expanded .src .sidebar { overflow-y: auto; flex-basis: var(--src-sidebar-width); + width: var(--src-sidebar-width); } .src-sidebar-expanded .src .sidebar > *:not(#src-sidebar-toggle) { diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index e1d674e7d04..be314317209 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -1337,6 +1337,13 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ // it gets the sidebar to restore its size. let desiredSidebarSize = null; + // Sidebar resize debouncer. + // + // The sidebar itself is resized instantly, but the body HTML can be too + // big for that, causing reflow jank. To reduce this, we queue up a separate + // animation frame and throttle it. + let pendingSidebarResizingFrame = false; + // If this page has no sidebar at all, bail out. const resizer = document.querySelector(".sidebar-resizer"); const sidebar = document.querySelector(".sidebar"); @@ -1360,12 +1367,26 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ if (isSrcPage) { window.rustdocCloseSourceSidebar(); updateLocalStorage("src-sidebar-width", null); + // [RUSTDOCIMPL] CSS variable fast path + // + // The sidebar width variable is attached to the element by + // storage.js, because the sidebar and resizer don't exist yet. + // But the resize code, in `resize()`, sets the property on the + // sidebar and resizer elements (which are the only elements that + // use the variable) to avoid recalculating CSS on the entire + // document on every frame. + // + // So, to clear it, we need to clear all three. document.documentElement.style.removeProperty("--src-sidebar-width"); + sidebar.style.removeProperty("--src-sidebar-width"); + resizer.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"); + sidebar.style.removeProperty("--desktop-sidebar-width"); + resizer.style.removeProperty("--desktop-sidebar-width"); } } @@ -1384,15 +1405,27 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ } } - // Call this to set the correct CSS variable and setting. - // This function doesn't enforce size constraints. Do that before calling it! + /** + * Call this to set the correct CSS variable and setting. + * This function doesn't enforce size constraints. Do that before calling it! + * + * @param {number} size - CSS px width of the sidebar. + */ function changeSidebarSize(size) { if (isSrcPage) { updateLocalStorage("src-sidebar-width", size); - document.documentElement.style.setProperty("--src-sidebar-width", size + "px"); + // [RUSTDOCIMPL] CSS variable fast path + // + // While this property is set on the HTML element at load time, + // because the sidebar isn't actually loaded yet, + // we scope this update to the sidebar to avoid hitting a slow + // path in WebKit. + sidebar.style.setProperty("--src-sidebar-width", size + "px"); + resizer.style.setProperty("--src-sidebar-width", size + "px"); } else { updateLocalStorage("desktop-sidebar-width", size); - document.documentElement.style.setProperty("--desktop-sidebar-width", size + "px"); + sidebar.style.setProperty("--desktop-sidebar-width", size + "px"); + resizer.style.setProperty("--desktop-sidebar-width", size + "px"); } } @@ -1424,6 +1457,19 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ const constrainedPos = Math.min(pos, window.innerWidth - BODY_MIN, SIDEBAR_MAX); changeSidebarSize(constrainedPos); desiredSidebarSize = constrainedPos; + if (pendingSidebarResizingFrame !== false) { + clearTimeout(pendingSidebarResizingFrame); + } + pendingSidebarResizingFrame = setTimeout(() => { + if (currentPointerId === null || pendingSidebarResizingFrame === false) { + return; + } + pendingSidebarResizingFrame = false; + document.documentElement.style.setProperty( + "--resizing-sidebar-width", + desiredSidebarSize + "px" + ); + }, 100); } } // Respond to the window resize event. @@ -1447,6 +1493,7 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ window.removeEventListener("pointermove", resize, false); window.removeEventListener("pointerup", stopResize, false); removeClass(document.documentElement, "sidebar-resizing"); + document.documentElement.style.removeProperty( "--resizing-sidebar-width"); if (resizer.releasePointerCapture) { resizer.releasePointerCapture(currentPointerId); currentPointerId = null; @@ -1472,6 +1519,8 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ window.addEventListener("pointerup", stopResize, false); addClass(resizer, "active"); addClass(document.documentElement, "sidebar-resizing"); + const pos = e.clientX - sidebar.offsetLeft - 3; + document.documentElement.style.setProperty( "--resizing-sidebar-width", pos + "px"); desiredSidebarSize = null; } resizer.addEventListener("pointerdown", initResize, false); diff --git a/src/librustdoc/html/static_files.rs b/src/librustdoc/html/static_files.rs index 080958b1cbc..ca9a78f51b3 100644 --- a/src/librustdoc/html/static_files.rs +++ b/src/librustdoc/html/static_files.rs @@ -100,7 +100,6 @@ static_files! { 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", -- cgit 1.4.1-3-g733a5 From fd9f1a7148b47722d617bd896a8ead7211762d22 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Wed, 11 Oct 2023 12:15:33 -0700 Subject: rustdoc: fix resize trouble with mobile --- src/librustdoc/html/static/js/main.js | 4 ++++ tests/rustdoc-gui/sidebar-resize-window.goml | 4 ++++ 2 files changed, 8 insertions(+) (limited to 'src/librustdoc/html/static/js') diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index be314317209..9ba5dc8a3a6 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -1305,6 +1305,7 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ // at most 400px. Otherwise, it would start out at the default size, then // grabbing the resize handle would suddenly cause it to jank to // its contraint-generated maximum. + const RUSTDOC_MOBILE_BREAKPOINT = 700; const BODY_MIN = 400; // At half-way past the minimum size, vanish the sidebar entirely const SIDEBAR_VANISH_THRESHOLD = SIDEBAR_MIN / 2; @@ -1474,6 +1475,9 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ } // Respond to the window resize event. window.addEventListener("resize", () => { + if (window.innerWidth < RUSTDOC_MOBILE_BREAKPOINT) { + return; + } stopResize(); if (desiredSidebarSize >= (window.innerWidth - BODY_MIN)) { changeSidebarSize(window.innerWidth - BODY_MIN); diff --git a/tests/rustdoc-gui/sidebar-resize-window.goml b/tests/rustdoc-gui/sidebar-resize-window.goml index 04321acc105..fb6baafda71 100644 --- a/tests/rustdoc-gui/sidebar-resize-window.goml +++ b/tests/rustdoc-gui/sidebar-resize-window.goml @@ -18,6 +18,10 @@ wait-for-property: (".sidebar", {"clientWidth": 500}, [NEAR]) // make the window small enough that the sidebar has to shrink set-window-size: (750, 600) wait-for-property: (".sidebar", {"clientWidth": 350}, [NEAR]) +assert-local-storage: {"rustdoc-desktop-sidebar-width": "350"} +set-window-size: (400, 600) +wait-for-css: (".sidebar", {"display": "block", "left": "-1000px"}) +assert-local-storage: {"rustdoc-desktop-sidebar-width": "350"} // grow the window again to make the sidebar bigger set-window-size: (1280, 600) -- cgit 1.4.1-3-g733a5