screen: add fit-to-window toggle
Adds a 'fit' toolbar toggle on the /screen VNC viewer. When on (the default), the canvas is CSS-scaled down to fit the browser viewport preserving aspect ratio — no more scrolling a desktop larger than the window. When off, the canvas renders at native resolution with scroll. The choice persists in localStorage. The canvas's intrinsic resolution is never touched — only its display size. sendPointer now rescales client coordinates by the canvas display-vs-intrinsic ratio so clicks stay accurate in fit mode (this also fixes a latent off-by-scale bug). Toolbar buttons share a .tbtn class for consistent styling. Issue #133 (fit toggle now; dynamic desktop resize tracked separately).
This commit is contained in:
parent
1f52746bd9
commit
305a32220b
1 changed files with 41 additions and 7 deletions
|
|
@ -30,6 +30,12 @@ html, body { height: 100%; background: var(--base); color: var(--text);
|
|||
}
|
||||
#toolbar a { color: var(--blue); text-decoration: none; font-size: 0.85rem; }
|
||||
#toolbar a:hover { text-decoration: underline; }
|
||||
.tbtn {
|
||||
padding: 0.15rem 0.5rem; font-size: 0.72rem; font-family: inherit;
|
||||
background: var(--surface0); color: var(--subtext0);
|
||||
border: 1px solid var(--surface1); border-radius: 4px; cursor: pointer;
|
||||
}
|
||||
.tbtn.active { color: var(--green); border-color: var(--green); }
|
||||
#status { margin-left: auto; font-size: 0.75rem; color: var(--subtext0); }
|
||||
#status.connected { color: var(--green); }
|
||||
#status.error { color: var(--red); }
|
||||
|
|
@ -50,7 +56,12 @@ html, body { height: 100%; background: var(--base); color: var(--text);
|
|||
width: 100%; height: calc(100% - 36px); overflow: auto;
|
||||
background: var(--crust);
|
||||
}
|
||||
/* Fit mode: centre the canvas and let CSS scale it down to the
|
||||
viewport (intrinsic resolution unchanged — see canvas.fit). */
|
||||
#canvas-wrap.fit { align-items: center; overflow: hidden; }
|
||||
canvas { display: block; cursor: default; }
|
||||
/* max-* on a replaced element shrinks it preserving aspect ratio. */
|
||||
canvas.fit { max-width: 100%; max-height: 100%; }
|
||||
#msg {
|
||||
position: fixed; bottom: 1rem; left: 50%; transform: translateX(-50%);
|
||||
background: var(--surface0); color: var(--yellow); border-radius: 6px;
|
||||
|
|
@ -64,10 +75,8 @@ canvas { display: block; cursor: default; }
|
|||
<div id="toolbar">
|
||||
<strong>🖥 screen</strong>
|
||||
<a href="/" title="back to agent page">← agent</a>
|
||||
<button id="debug-toggle" title="Toggle RFB debug log"
|
||||
style="margin-left:0.5rem;padding:0.15rem 0.5rem;font-size:0.72rem;
|
||||
background:var(--surface0);color:var(--subtext0);border:1px solid var(--surface1);
|
||||
border-radius:4px;cursor:pointer;">debug</button>
|
||||
<button id="fit-toggle" class="tbtn" title="Toggle fit-to-window scaling">⤢ fit</button>
|
||||
<button id="debug-toggle" class="tbtn" title="Toggle RFB debug log">debug</button>
|
||||
<span id="status">connecting…</span>
|
||||
</div>
|
||||
<div id="canvas-wrap"><canvas id="c"></canvas></div>
|
||||
|
|
@ -92,15 +101,36 @@ canvas { display: block; cursor: default; }
|
|||
const msg = document.getElementById('msg');
|
||||
const debugLog = document.getElementById('debug-log');
|
||||
const debugBtn = document.getElementById('debug-toggle');
|
||||
const fitBtn = document.getElementById('fit-toggle');
|
||||
const canvasWrap = document.getElementById('canvas-wrap');
|
||||
|
||||
// --- Debug log ---
|
||||
let debugVisible = false;
|
||||
debugBtn.addEventListener('click', () => {
|
||||
debugVisible = !debugVisible;
|
||||
debugLog.style.display = debugVisible ? 'block' : 'none';
|
||||
debugBtn.style.color = debugVisible ? 'var(--green)' : 'var(--subtext0)';
|
||||
debugBtn.classList.toggle('active', debugVisible);
|
||||
});
|
||||
|
||||
// --- Fit-to-window toggle ---
|
||||
// Scales the canvas down via CSS so the whole desktop is visible
|
||||
// without scrolling. The canvas's intrinsic resolution is untouched;
|
||||
// only its displayed size changes (see canvas.fit / #canvas-wrap.fit).
|
||||
// Pointer coordinates are rescaled in sendPointer to stay accurate.
|
||||
// The choice is persisted in localStorage; default is fit-on.
|
||||
let fitMode = localStorage.getItem('screen-fit') !== 'off';
|
||||
function applyFitMode() {
|
||||
canvas.classList.toggle('fit', fitMode);
|
||||
canvasWrap.classList.toggle('fit', fitMode);
|
||||
fitBtn.classList.toggle('active', fitMode);
|
||||
}
|
||||
fitBtn.addEventListener('click', () => {
|
||||
fitMode = !fitMode;
|
||||
localStorage.setItem('screen-fit', fitMode ? 'on' : 'off');
|
||||
applyFitMode();
|
||||
});
|
||||
applyFitMode();
|
||||
|
||||
function hex(bytes) {
|
||||
return Array.from(bytes).map(b => b.toString(16).padStart(2,'0')).join(' ');
|
||||
}
|
||||
|
|
@ -570,8 +600,12 @@ canvas { display: block; cursor: default; }
|
|||
|
||||
function sendPointer(ev) {
|
||||
const r = canvas.getBoundingClientRect();
|
||||
const x = Math.max(0, Math.min(fbW-1, ev.clientX - r.left));
|
||||
const y = Math.max(0, Math.min(fbH-1, ev.clientY - r.top));
|
||||
// In fit mode the canvas is CSS-scaled, so the rendered rect differs
|
||||
// from the intrinsic resolution — map client coords back to fb pixels.
|
||||
const sx = r.width ? canvas.width / r.width : 1;
|
||||
const sy = r.height ? canvas.height / r.height : 1;
|
||||
const x = Math.max(0, Math.min(fbW-1, Math.round((ev.clientX - r.left) * sx)));
|
||||
const y = Math.max(0, Math.min(fbH-1, Math.round((ev.clientY - r.top) * sy)));
|
||||
let mask = 0;
|
||||
if (ev.buttons & 1) mask |= 1;
|
||||
if (ev.buttons & 4) mask |= 2;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue