Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve multitouch and touch indicator behaviour on lost focus #551

Merged
merged 14 commits into from
Sep 19, 2024
Merged
14 changes: 7 additions & 7 deletions packages/vscode-extension/src/webview/components/Preview.css
Original file line number Diff line number Diff line change
Expand Up @@ -122,22 +122,22 @@
background-color: var(--swm-phone-content-loading-background);
}

.touch-marker {
.touch-indicator {
position: absolute;
top: var(--y, 50%);
left: var(--x, 50%);
background-color: var(--touch-marker-background);
background-color: var(--touch-indicator-background);
border-radius: 50%;
border: 1px solid;
border-color: var(--touch-marker-border);
border-color: var(--touch-indicator-border);
width: var(--size);
height: var(--size);
transform: translate(-50%, -50%);
opacity: 0.6;
}

.touch-marker.pressed {
background-color: var(--touch-marker-pressed-background);
border-color: var(--touch-marker-pressed-border);
box-shadow: var(--touch-marker-pressed-shadow);
.touch-indicator.pressed {
background-color: var(--touch-indicator-pressed-background);
border-color: var(--touch-indicator-pressed-border);
box-shadow: var(--touch-indicator-pressed-shadow);
}
65 changes: 49 additions & 16 deletions packages/vscode-extension/src/webview/components/Preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ function DeviceFrame({ device, isFrameDisabled }: DeviceFrameProps) {
);
}

function TouchPointMarker({ isPressing }: { isPressing: boolean }) {
return <div className={`touch-marker ${isPressing ? "pressed" : ""}`}></div>;
function TouchPointIndicator({ isPressing }: { isPressing: boolean }) {
return <div className={`touch-indicator ${isPressing ? "pressed" : ""}`}></div>;
}

type ButtonGroupLeftProps = {
Expand Down Expand Up @@ -178,6 +178,8 @@ type InspectStackData = {
type Props = {
isInspecting: boolean;
setIsInspecting: (isInspecting: boolean) => void;
isPressing: boolean;
setIsPressing: (isPressing: boolean) => void;
zoomLevel: ZoomLevelType;
onZoomChanged: (zoomLevel: ZoomLevelType) => void;
};
Expand All @@ -197,11 +199,18 @@ function calculateMirroredTouchPosition(touchPoint: Point, anchorPoint: Point) {
return { x: clampedX, y: clampedY };
}

function Preview({ isInspecting, setIsInspecting, zoomLevel, onZoomChanged }: Props) {
function Preview({
isInspecting,
setIsInspecting,
isPressing,
setIsPressing,
zoomLevel,
onZoomChanged,
}: Props) {
const wrapperDivRef = useRef<HTMLDivElement>(null);
const [isPressing, setIsPressing] = useState(false);
const [isMultiTouching, setIsMultiTouching] = useState(false);
const [isPanning, setIsPanning] = useState(false);
const [isTouchAreaActive, setIsTouchAreaActive] = useState(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you really need this one to be expressed as state? Given you only read it in mouse event handler. Maybe you could just check document.hasFocus there?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, it was unnecessary to use state there, so I've used listeners and document.hasFocus instead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isTouchAreaActive is not used, delete it?

const [touchPoint, setTouchPoint] = useState<Point>({ x: 0.5, y: 0.5 });
const [anchorPoint, setAnchorPoint] = useState<Point>({ x: 0.5, y: 0.5 });
const previewRef = useRef<HTMLImageElement>(null);
Expand Down Expand Up @@ -367,17 +376,29 @@ function Preview({ isInspecting, setIsInspecting, zoomLevel, onZoomChanged }: Pr
}
}

function onMouseEnter(e: MouseEvent<HTMLDivElement>) {
e.preventDefault();
wrapperDivRef.current!.focus();

if (isPressing) {
if (isMultiTouching) {
sendMultiTouch(e, "Down");
} else {
sendTouch(e, "Down");
}
}
setTouchPoint(getTouchPosition(e));
}

function onMouseLeave(e: MouseEvent<HTMLDivElement>) {
e.preventDefault();
if (isPressing) {
if (isMultiTouching) {
setIsMultiTouching(false);
setIsPanning(false);
sendMultiTouch(e, "Up");
} else {
sendTouch(e, "Up");
setIsPressing(false);
}
setIsPressing(false);
}

if (isInspecting) {
Expand All @@ -391,6 +412,18 @@ function Preview({ isInspecting, setIsInspecting, zoomLevel, onZoomChanged }: Pr
e.preventDefault();
}

useEffect(() => {
function onBlurChange() {
if (!document.hasFocus()) {
setIsPanning(false);
setIsMultiTouching(false);
setIsPressing(false);
}
}
addEventListener("blur", onBlurChange, true);
return () => removeEventListener("blur", onBlurChange, true);
}, []);

useEffect(() => {
function dispatchPaste(e: ClipboardEvent) {
if (document.activeElement === wrapperDivRef.current) {
Expand All @@ -402,7 +435,6 @@ function Preview({ isInspecting, setIsInspecting, zoomLevel, onZoomChanged }: Pr
}
}
}

addEventListener("paste", dispatchPaste);
return () => {
removeEventListener("paste", dispatchPaste);
Expand Down Expand Up @@ -461,6 +493,7 @@ function Preview({ isInspecting, setIsInspecting, zoomLevel, onZoomChanged }: Pr
onMouseDown,
onMouseMove,
onMouseUp,
onMouseEnter,
onMouseLeave,
onContextMenu,
};
Expand All @@ -473,8 +506,8 @@ function Preview({ isInspecting, setIsInspecting, zoomLevel, onZoomChanged }: Pr
});

const mirroredTouchPosition = calculateMirroredTouchPosition(touchPoint, anchorPoint);
const normalTouchMarkerSize = 33;
const smallTouchMarkerSize = 9;
const normalTouchIndicatorSize = 33;
const smallTouchIndicatorSize = 9;

return (
<>
Expand All @@ -501,29 +534,29 @@ function Preview({ isInspecting, setIsInspecting, zoomLevel, onZoomChanged }: Pr
style={{
"--x": `${touchPoint.x * 100}%`,
"--y": `${touchPoint.y * 100}%`,
"--size": `${normalTouchMarkerSize}px`,
"--size": `${normalTouchIndicatorSize}px`,
}}>
<TouchPointMarker isPressing={isPressing} />
<TouchPointIndicator isPressing={isPressing} />
</div>
)}
{isMultiTouching && (
<div
style={{
"--x": `${anchorPoint.x * 100}%`,
"--y": `${anchorPoint.y * 100}%`,
"--size": `${smallTouchMarkerSize}px`,
"--size": `${smallTouchIndicatorSize}px`,
}}>
<TouchPointMarker isPressing={false} />
<TouchPointIndicator isPressing={false} />
</div>
)}
{isMultiTouching && (
<div
style={{
"--x": `${mirroredTouchPosition.x * 100}%`,
"--y": `${mirroredTouchPosition.y * 100}%`,
"--size": `${normalTouchMarkerSize}px`,
"--size": `${normalTouchIndicatorSize}px`,
}}>
<TouchPointMarker isPressing={isPressing} />
<TouchPointIndicator isPressing={isPressing} />
</div>
)}

Expand Down
12 changes: 6 additions & 6 deletions packages/vscode-extension/src/webview/styles/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,12 @@
--grey-dark-60: #999999;
--grey-dark-40: #b3b3b3;

/* Touch marker */
--touch-marker-background: var(--grey-dark-40);
--touch-marker-border: var(--grey-dark-80);
--touch-marker-pressed-background: var(--green-light-100);
--touch-marker-pressed-border: var(--green-dark-120);
--touch-marker-pressed-shadow: 2px 2px 6px 1px rgba(0, 0, 0, 0.8);
/* Touch indicator */
--touch-indicator-background: var(--grey-dark-40);
--touch-indicator-border: var(--grey-dark-80);
--touch-indicator-pressed-background: var(--green-light-100);
--touch-indicator-pressed-border: var(--green-dark-120);
--touch-indicator-pressed-shadow: 2px 2px 6px 1px rgba(0, 0, 0, 0.8);
}

/* Light theme */
Expand Down
22 changes: 20 additions & 2 deletions packages/vscode-extension/src/webview/views/PreviewView.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect, useCallback } from "react";
import { useState, useEffect, useCallback, MouseEvent } from "react";
import { vscode } from "../utilities/vscode";
import Preview from "../components/Preview";
import IconButton from "../components/shared/IconButton";
Expand All @@ -25,6 +25,7 @@ function PreviewView() {
const { reportIssue } = useUtils();

const [isInspecting, setIsInspecting] = useState(false);
const [isPressing, setIsPressing] = useState(false);
const zoomLevel = projectState.previewZoom ?? "Fit";
const onZoomChanged = useCallback(
(zoom: ZoomLevelType) => {
Expand Down Expand Up @@ -100,8 +101,23 @@ function PreviewView() {
);
}

function onMouseDown(e: MouseEvent<HTMLDivElement>) {
e.preventDefault();
setIsPressing(true);
}

function onMouseUp(e: MouseEvent<HTMLDivElement>) {
e.preventDefault();
setIsPressing(false);
}

const touchHandlers = {
onMouseDown,
onMouseUp,
};

return (
<div className="panel-view">
<div className="panel-view" {...touchHandlers}>
<div className="button-group-top">
<UrlBar key={resetKey} disabled={devicesNotFound} />
<div className="spacer" />
Expand Down Expand Up @@ -132,6 +148,8 @@ function PreviewView() {
key={selectedDevice.id}
isInspecting={isInspecting}
setIsInspecting={setIsInspecting}
isPressing={isPressing}
setIsPressing={setIsPressing}
zoomLevel={zoomLevel}
onZoomChanged={onZoomChanged}
/>
Expand Down
Loading