diff --git a/mobile/src/main.ts b/mobile/src/main.ts index b6cf4740..17fb4ba8 100644 --- a/mobile/src/main.ts +++ b/mobile/src/main.ts @@ -778,6 +778,12 @@ function terminalTheme(): ITheme { const theme: Record = dark ? { background: "#191b20", foreground: "#e2e5eb", cursor: "#78a8f5", cursorAccent: "#191b20", selectionBackground: "#78a8f555" } : { background: "#ffffff", foreground: "#0f1419", cursor: "#1f6bf0", cursorAccent: "#ffffff", selectionBackground: "#1f6bf033" }; + // xterm's scrollbar, drawn to match the system indicator WebKit gives the + // pan (style.css shapes it). + const bar = dark ? "#ffffff" : "#000000"; + theme.scrollbarSliderBackground = `${bar}59`; + theme.scrollbarSliderHoverBackground = `${bar}59`; + theme.scrollbarSliderActiveBackground = `${bar}73`; NAMES.forEach((n, i) => { theme[n] = ansi[i]; theme[`bright${n[0].toUpperCase()}${n.slice(1)}`] = ansi[i + 8]; @@ -1080,7 +1086,25 @@ function terminalScreen(host: Host, place: Place, pane: PaneView, title: string) zoom.replaceChildren(ico(readable ? "fit" : "zoom")); zoom.ariaLabel = readable ? "Fit the whole width" : "Make the text readable"; follow(); + // The new font size is laid out on the next frame. + requestAnimationFrame(pinScrollbar); }; + // xterm puts its scrollbar at the right of its own box, which pans with + // the text; shift it so it stays at the right of what is on screen. The + // `translate` property, not `transform`, so it never fights xterm's own + // styling of the bar. + const pinScrollbar = () => { + const box = screenEl.querySelector(".xterm"); + const bar = box?.querySelector(".xterm-scrollable-element > .scrollbar.vertical"); + if (!box || !bar) return; + if (!screenEl.classList.contains("panning")) { + bar.style.translate = ""; + return; + } + const edge = screenEl.scrollLeft + screenEl.clientWidth - parseFloat(getComputedStyle(screenEl).paddingRight); + bar.style.translate = `${Math.min(0, edge - (box.offsetLeft + box.offsetWidth))}px 0`; + }; + screenEl.addEventListener("scroll", pinScrollbar, { passive: true }); const follow = () => { if (!screenEl.classList.contains("panning")) return; const cell = screenEl.scrollWidth / cols; diff --git a/mobile/src/style.css b/mobile/src/style.css index 79bab1ac..648384ba 100644 --- a/mobile/src/style.css +++ b/mobile/src/style.css @@ -1064,6 +1064,24 @@ code { overflow-x: auto; } +/* Panning, xterm's box has to span every column: at the view's width it ends + partway across the pane, so its scrollback stops taking touches past that + point and its scrollbar pans off with the text. `pinScrollbar` keeps the + bar at the visible edge. */ +.term.panning .xterm { + width: max-content; +} + +/* xterm's vertical scrollbar is VS Code's: a 14px square slider. Shape it like + the thin rounded indicator WebKit draws for the sideways pan, so the two + axes read as one kind of thing. The colours come from the theme. */ +.term .xterm-scrollable-element > .scrollbar.vertical > .slider { + left: auto !important; + right: 2px; + width: 3px !important; + border-radius: 1.5px; +} + .term .xterm { -webkit-user-select: text; user-select: text;