Improve(Web): Touch-mode visual cursor follows remote IDC_* state
This commit is contained in:
@@ -951,6 +951,9 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
/* Cursor overlay (touchpad mode). Per-variant classes are swapped via JS based on
|
||||
the remote IDC_* index. Each variant overrides --hx/--hy so the icon's tip
|
||||
(hotspot), not the div's top-left, lands on the reported cursor position. */
|
||||
.cursor-overlay {
|
||||
position: fixed;
|
||||
width: 24px;
|
||||
@@ -958,15 +961,65 @@
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
z-index: 1002;
|
||||
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="%23fff" stroke="%23000" stroke-width="1.5" d="M4 4l7 17 2.5-6.5L20 12z"/></svg>') no-repeat;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
filter: drop-shadow(2px 2px 3px rgba(0,0,0,0.6));
|
||||
transform-origin: 0 0;
|
||||
/* SVG arrow tip is at (4,4) inside the 24x24 viewBox; shift overlay so the tip,
|
||||
not the div's top-left, lands on the reported cursor position. */
|
||||
transform: translate(-4px, -4px);
|
||||
--hx: 4px;
|
||||
--hy: 4px;
|
||||
transform: translate(calc(-1 * var(--hx)), calc(-1 * var(--hy)));
|
||||
/* IDC_ARROW (default) — also the fallback for unmapped indices. */
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="white" stroke="black" stroke-width="1.5" d="M4 4l7 17 2.5-6.5L20 12z"/></svg>');
|
||||
}
|
||||
.cursor-overlay.active { display: block; }
|
||||
/* IDC_IBEAM (5) — text input */
|
||||
.cursor-overlay.cursor-ibeam {
|
||||
--hx: 8px; --hy: 12px;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="white" stroke-width="3" d="M4 3h8M8 3v18M4 21h8"/><path fill="none" stroke="black" stroke-width="1" d="M4 3h8M8 3v18M4 21h8"/></svg>');
|
||||
}
|
||||
/* IDC_HAND (3) — Windows-style pointing hand: tall index finger, thumb on the left,
|
||||
three visible bent fingers (middle/ring/pinky) with descending heights, rounded palm. */
|
||||
.cursor-overlay.cursor-hand {
|
||||
--hx: 11.5px; --hy: 1.5px;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="white" stroke="black" stroke-width="1.2" stroke-linejoin="round" stroke-linecap="round" d="M10 3 a1.5 1.5 0 0 1 3 0 v7 q1.5 -7 3 0 q1.5 -6 3 0 q1.5 -5 3 0 q1 1 1 2 v5 a4 4 0 0 1 -4 4 h-7 a4 4 0 0 1 -4 -4 v-2 q-2 0 -2 -2 q0 -2 2 -2 q1 0 1 1 q1 -1 1 -2 z"/></svg>');
|
||||
}
|
||||
/* IDC_WAIT (15) — busy hourglass */
|
||||
.cursor-overlay.cursor-wait {
|
||||
--hx: 12px; --hy: 12px;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="white" stroke="black" stroke-width="1.5" stroke-linejoin="round" d="M6 3h12v4l-5 5 5 5v4H6v-4l5-5-5-5z"/></svg>');
|
||||
}
|
||||
/* IDC_NO (7) — forbidden */
|
||||
.cursor-overlay.cursor-no {
|
||||
--hx: 12px; --hy: 12px;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8" fill="none" stroke="white" stroke-width="3"/><line x1="6.5" y1="6.5" x2="17.5" y2="17.5" stroke="white" stroke-width="3"/><circle cx="12" cy="12" r="8" fill="none" stroke="black" stroke-width="1.5"/><line x1="6.5" y1="6.5" x2="17.5" y2="17.5" stroke="black" stroke-width="1.5"/></svg>');
|
||||
}
|
||||
/* IDC_SIZEALL (9) — 4-way move */
|
||||
.cursor-overlay.cursor-move {
|
||||
--hx: 12px; --hy: 12px;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="white" stroke="black" stroke-width="1.5" stroke-linejoin="round" d="M12 3 9 7h2v4H7V9l-4 3 4 3v-2h4v4H9l3 4 3-4h-2v-4h4v2l4-3-4-3v2h-4V7h2z"/></svg>');
|
||||
}
|
||||
/* IDC_SIZENS (11) — vertical resize */
|
||||
.cursor-overlay.cursor-sizens {
|
||||
--hx: 12px; --hy: 12px;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="white" stroke="black" stroke-width="1.5" stroke-linejoin="round" d="M12 3 9 7h2v10H9l3 4 3-4h-2V7h2z"/></svg>');
|
||||
}
|
||||
/* IDC_SIZEWE (13) — horizontal resize */
|
||||
.cursor-overlay.cursor-sizewe {
|
||||
--hx: 12px; --hy: 12px;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="white" stroke="black" stroke-width="1.5" stroke-linejoin="round" d="M3 12 7 9v2h10V9l4 3-4 3v-2H7v2z"/></svg>');
|
||||
}
|
||||
/* IDC_SIZENWSE (12) — diagonal NW-SE resize (\\). Rotating vertical NS arrow -45° (CCW)
|
||||
tilts the top-left to upper-left and bottom-right to lower-right. */
|
||||
.cursor-overlay.cursor-sizenwse {
|
||||
--hx: 12px; --hy: 12px;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g transform="rotate(-45 12 12)"><path fill="white" stroke="black" stroke-width="1.5" stroke-linejoin="round" d="M12 3 9 7h2v10H9l3 4 3-4h-2V7h2z"/></g></svg>');
|
||||
}
|
||||
/* IDC_SIZENESW (10) — diagonal NE-SW resize (//). Rotating vertical NS arrow +45° (CW)
|
||||
tilts the top toward upper-right and bottom toward lower-left. */
|
||||
.cursor-overlay.cursor-sizenesw {
|
||||
--hx: 12px; --hy: 12px;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g transform="rotate(45 12 12)"><path fill="white" stroke="black" stroke-width="1.5" stroke-linejoin="round" d="M12 3 9 7h2v10H9l3 4 3-4h-2V7h2z"/></g></svg>');
|
||||
}
|
||||
/* Input shortcut bar - below canvas, portrait mode only */
|
||||
.input-shortcuts {
|
||||
position: absolute;
|
||||
@@ -1465,15 +1518,11 @@
|
||||
initDecoder(msg.width, msg.height);
|
||||
break;
|
||||
case 'cursor':
|
||||
// Update remote cursor style (only for desktop in control mode)
|
||||
// Update remote cursor visual: native CSS cursor on desktop,
|
||||
// overlay variant on touch. 254=custom (unsupported), 255=unsupported.
|
||||
currentCursorIndex = msg.index;
|
||||
if (controlEnabled && !isTouchDevice) {
|
||||
const canvas = document.getElementById('screen-canvas');
|
||||
// 254=custom cursor (not supported in web), 255=unsupported -> default
|
||||
const cssCursor = (msg.index >= 0 && msg.index < cursorMap.length)
|
||||
? cursorMap[msg.index]
|
||||
: 'default';
|
||||
canvas.style.cursor = cssCursor;
|
||||
if (controlEnabled) {
|
||||
applyRemoteCursor(msg.index);
|
||||
}
|
||||
break;
|
||||
case 'device_offline':
|
||||
@@ -2274,6 +2323,39 @@
|
||||
];
|
||||
let currentCursorIndex = 1; // Default: arrow
|
||||
|
||||
// IDC_* index -> CSS class on .cursor-overlay (touch-mode visual cursor).
|
||||
// Indices not listed (0,2,4,6,8,14, custom) fall back to default arrow.
|
||||
const cursorOverlayClassMap = {
|
||||
3: 'cursor-hand',
|
||||
5: 'cursor-ibeam',
|
||||
7: 'cursor-no',
|
||||
9: 'cursor-move',
|
||||
10: 'cursor-sizenesw',
|
||||
11: 'cursor-sizens',
|
||||
12: 'cursor-sizenwse',
|
||||
13: 'cursor-sizewe',
|
||||
15: 'cursor-wait'
|
||||
};
|
||||
|
||||
// Apply the remote cursor: native CSS cursor on desktop, overlay variant on touch.
|
||||
function applyRemoteCursor(index) {
|
||||
if (isTouchDevice) {
|
||||
const overlay = document.getElementById('cursor-overlay');
|
||||
if (!overlay) return;
|
||||
const wasActive = overlay.classList.contains('active');
|
||||
overlay.className = 'cursor-overlay' + (wasActive ? ' active' : '');
|
||||
const cls = cursorOverlayClassMap[index];
|
||||
if (cls) overlay.classList.add(cls);
|
||||
} else {
|
||||
const canvas = document.getElementById('screen-canvas');
|
||||
if (!canvas) return;
|
||||
const cssCursor = (index >= 0 && index < cursorMap.length)
|
||||
? cursorMap[index]
|
||||
: 'default';
|
||||
canvas.style.cursor = cssCursor;
|
||||
}
|
||||
}
|
||||
|
||||
// Floating toolbar state
|
||||
let toolbarVisible = false;
|
||||
let toolbarHideTimer = null;
|
||||
@@ -2424,20 +2506,17 @@
|
||||
// Cursor handling
|
||||
const canvas = document.getElementById('screen-canvas');
|
||||
const cursorOverlay = document.getElementById('cursor-overlay');
|
||||
// Touch devices: hide browser cursor, show overlay (touchpad mode)
|
||||
// Desktop: use remote cursor style when control enabled
|
||||
// Touch: hide native cursor and show our overlay (touchpad mode).
|
||||
// Desktop: native CSS cursor is set by applyRemoteCursor below.
|
||||
if (controlEnabled && isTouchDevice) {
|
||||
canvas.style.cursor = 'none';
|
||||
} else if (controlEnabled && !isTouchDevice) {
|
||||
// Apply current remote cursor
|
||||
const cssCursor = (currentCursorIndex >= 0 && currentCursorIndex < cursorMap.length)
|
||||
? cursorMap[currentCursorIndex]
|
||||
: 'default';
|
||||
canvas.style.cursor = cssCursor;
|
||||
} else {
|
||||
} else if (!controlEnabled) {
|
||||
canvas.style.cursor = 'default';
|
||||
}
|
||||
cursorOverlay.classList.toggle('active', controlEnabled && isTouchDevice);
|
||||
if (controlEnabled) {
|
||||
applyRemoteCursor(currentCursorIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// Update cursor overlay position (accounting for zoom/pan transform)
|
||||
@@ -3283,7 +3362,8 @@
|
||||
const qcMouse = document.getElementById('qc-mouse');
|
||||
if (qcMouse) qcMouse.classList.remove('active');
|
||||
document.getElementById('screen-canvas').style.cursor = 'default';
|
||||
document.getElementById('cursor-overlay').classList.remove('active');
|
||||
// Strip any cursor-* variant on the overlay, leaving the bare default arrow.
|
||||
document.getElementById('cursor-overlay').className = 'cursor-overlay';
|
||||
currentCursorIndex = 1; // Reset to default arrow
|
||||
|
||||
// Reset zoom state
|
||||
|
||||
Reference in New Issue
Block a user