Browse Source

[DesignerHost/HTML]: improve high-dpi screen behavior (#19996)

* DesignerHost: improve high-dpi screen behavior
handle previewer dpi changed event

* fix test

---------

Co-authored-by: ijklam <43789618+Tangent-90@users.noreply.github.com>
pull/20166/head
ijklam 2 months ago
committed by GitHub
parent
commit
34b00c5169
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 46
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx
  2. 6
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerEventMessageBase.ts
  3. 4
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerMovedEventMessage.ts
  4. 4
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerPressedEventMessage.ts
  5. 4
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerReleasedEventMessage.ts
  6. 4
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/ScrollEventMessage.ts
  7. 6
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/PreviewerServerConnection.ts
  8. 6
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/index.html
  9. 1
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/tsconfig.json
  10. 8
      tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/Models/InputEventTests.ts

46
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx

@ -11,6 +11,7 @@ interface PreviewerPresenterProps {
export class PreviewerPresenter extends React.Component<PreviewerPresenterProps> {
private canvasRef: React.RefObject<HTMLCanvasElement>;
private scale: number = 1;
constructor(props: PreviewerPresenterProps) {
super(props);
@ -28,6 +29,23 @@ export class PreviewerPresenter extends React.Component<PreviewerPresenterProps>
componentDidMount(): void {
this.updateCanvas(this.canvasRef.current, this.props.conn.currentFrame);
if (parent != null && parent !== window)
{
window.addEventListener('message', (event) => {
if (!event.origin.startsWith('vscode-webview://')) {
return;
}
// Handle previewer dpi changed event
if (event.data.type === 'scaling') {
const scale = Number(event.data.scale);
if (Number.isFinite(scale) && scale > 0) {
this.scale = event.data.scale;
}
}
});
}
}
componentDidUpdate(prevProps: Readonly<PreviewerPresenterProps>, prevState: Readonly<{}>, snapshot?: any): void {
@ -48,39 +66,59 @@ export class PreviewerPresenter extends React.Component<PreviewerPresenterProps>
updateCanvas(canvas: HTMLCanvasElement | null, frame: PreviewerFrame | null) {
if (!canvas)
return;
const oldWidth = canvas.style.width;
const oldHeight = canvas.style.height;
if (frame == null){
canvas.width = canvas.height = 1;
canvas.style.width = canvas.style.height = `1px`;
canvas.getContext('2d')!.clearRect(0,0,1,1);
}
else {
canvas.width = frame.data.width;
canvas.height = frame.data.height;
// To prevent blurry images on high-DPI screens
canvas.style.width = `${frame.data.width / window.devicePixelRatio}px`;
canvas.style.height = `${frame.data.height / window.devicePixelRatio}px`;
const ctx = canvas.getContext('2d')!;
ctx.putImageData(frame.data, 0,0);
}
if (oldWidth !== canvas.style.width || oldHeight !== canvas.style.height)
{
// Inform the parent page (a VSCode extention) the canvas size has changed
parent?.postMessage({
type: 'resize',
width: canvas.width / window.devicePixelRatio,
height: canvas.height / window.devicePixelRatio,
}, '*');
}
}
handleMouseDown(e: React.MouseEvent) {
e.preventDefault();
const pointerPressedEventMessage = new PointerPressedEventMessage(e);
const pointerPressedEventMessage = new PointerPressedEventMessage(e, this.scale / window.devicePixelRatio);
this.props.conn.sendMouseEvent(pointerPressedEventMessage);
}
handleMouseUp(e: React.MouseEvent) {
e.preventDefault();
const pointerReleasedEventMessage = new PointerReleasedEventMessage(e);
const pointerReleasedEventMessage = new PointerReleasedEventMessage(e, this.scale / window.devicePixelRatio);
this.props.conn.sendMouseEvent(pointerReleasedEventMessage);
}
handleMouseMove(e: React.MouseEvent) {
e.preventDefault();
const pointerMovedEventMessage = new PointerMovedEventMessage(e);
const pointerMovedEventMessage = new PointerMovedEventMessage(e, this.scale / window.devicePixelRatio);
this.props.conn.sendMouseEvent(pointerMovedEventMessage);
}
handleWheel(e: React.WheelEvent) {
e.preventDefault();
const scrollEventMessage = new ScrollEventMessage(e);
const scrollEventMessage = new ScrollEventMessage(e, this.scale / window.devicePixelRatio);
this.props.conn.sendMouseEvent(scrollEventMessage);
}

6
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerEventMessageBase.ts

@ -5,9 +5,9 @@ export abstract class PointerEventMessageBase extends InputEventMessageBase {
public readonly x: number;
public readonly y: number;
protected constructor(e: React.MouseEvent) {
protected constructor(e: React.MouseEvent, scale: number) {
super(e);
this.x = e.clientX;
this.y = e.clientY;
this.x = e.clientX / scale;
this.y = e.clientY / scale;
}
}

4
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerMovedEventMessage.ts

@ -2,8 +2,8 @@
import {PointerEventMessageBase} from "./PointerEventMessageBase";
export class PointerMovedEventMessage extends PointerEventMessageBase {
constructor(e: React.MouseEvent) {
super(e);
constructor(e: React.MouseEvent, scale: number) {
super(e, scale);
}
public toString = () : string => {

4
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerPressedEventMessage.ts

@ -6,8 +6,8 @@ import {getMouseButton} from "./MouseEventHelpers";
export class PointerPressedEventMessage extends PointerEventMessageBase {
public readonly button: MouseButton
constructor(e: React.MouseEvent) {
super(e);
constructor(e: React.MouseEvent, scale: number) {
super(e, scale);
this.button = getMouseButton(e);
}

4
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerReleasedEventMessage.ts

@ -6,8 +6,8 @@ import {getMouseButton} from "./MouseEventHelpers";
export class PointerReleasedEventMessage extends PointerEventMessageBase {
public readonly button: MouseButton
constructor(e: React.MouseEvent) {
super(e);
constructor(e: React.MouseEvent, scale: number) {
super(e, scale);
this.button = getMouseButton(e);
}

4
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/ScrollEventMessage.ts

@ -5,8 +5,8 @@ export class ScrollEventMessage extends PointerEventMessageBase {
public readonly deltaX: number;
public readonly deltaY: number;
constructor(e: React.WheelEvent) {
super(e);
constructor(e: React.WheelEvent, scale: number) {
super(e, scale);
this.deltaX = -e.deltaX;
this.deltaY = -e.deltaY;
}

6
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/PreviewerServerConnection.ts

@ -6,6 +6,12 @@ export interface PreviewerFrame {
dpiY: number;
}
declare global {
interface Window {
avaloniaPreviewerSecurityCookie: string;
}
}
export class PreviewerServerConnection {
private nextFrame = {
width: 0,

6
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/index.html

@ -5,7 +5,11 @@
<meta charset="UTF-8">
<title>Avalonia XAML previewer web edition</title>
</head>
<body>
<!-- Removes the default margin of the body element.
Most browsers apply a default margin (typically 8px) to the body element
Setting it to 0 makes the content flush against the browser window edges, eliminating white space -->
<body style="margin: 0;">
<div id="app">
<center>Loading...</center>
</div>

1
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/tsconfig.json

@ -13,7 +13,6 @@
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": false,
"baseUrl": ".",
"experimentalDecorators": true,

8
tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/Models/InputEventTests.ts

@ -58,17 +58,17 @@ describe("Input event tests", () => {
.object()
it("PointerMovedEventMessage", () => {
const message = new PointerMovedEventMessage(mouseEvent)
const message = new PointerMovedEventMessage(mouseEvent, 1)
expect(message.toString())
.equal(`pointer-moved:${modifiers}:${x}:${y}`)
})
it("PointerPressedEventMessage", () => {
const message = new PointerPressedEventMessage(mouseEvent)
const message = new PointerPressedEventMessage(mouseEvent, 1)
expect(message.toString())
.equal(`pointer-pressed:${modifiers}:${x}:${y}:${button}`)
})
it("PointerReleasedEventMessage", () => {
const message = new PointerReleasedEventMessage(mouseEvent)
const message = new PointerReleasedEventMessage(mouseEvent, 1)
expect(message.toString())
.equal(`pointer-released:${modifiers}:${x}:${y}:${button}`)
})
@ -85,7 +85,7 @@ describe("Input event tests", () => {
.setup(x => x.deltaX).returns(-deltaX)
.setup(x => x.deltaY).returns(-deltaY)
.object()
const message = new ScrollEventMessage(wheelEvent)
const message = new ScrollEventMessage(wheelEvent, 1)
expect(message.toString())
.equal(`scroll:${modifiers}:${x}:${y}:${deltaX}:${deltaY}`)

Loading…
Cancel
Save