diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx index 96d2f45fdc..81d9943885 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx @@ -11,6 +11,7 @@ interface PreviewerPresenterProps { export class PreviewerPresenter extends React.Component { private canvasRef: React.RefObject; + private scale: number = 1; constructor(props: PreviewerPresenterProps) { super(props); @@ -28,6 +29,23 @@ export class PreviewerPresenter extends React.Component 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, prevState: Readonly<{}>, snapshot?: any): void { @@ -48,39 +66,59 @@ export class PreviewerPresenter extends React.Component 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); } diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerEventMessageBase.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerEventMessageBase.ts index c9de132ff4..89e3449074 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerEventMessageBase.ts +++ b/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; } } diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerMovedEventMessage.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerMovedEventMessage.ts index 3f782d1d46..d621ffd4a1 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerMovedEventMessage.ts +++ b/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 => { diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerPressedEventMessage.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerPressedEventMessage.ts index 0ae06cc064..9ec2028e3b 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerPressedEventMessage.ts +++ b/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); } diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerReleasedEventMessage.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerReleasedEventMessage.ts index fb1477b554..f8cfb68007 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerReleasedEventMessage.ts +++ b/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); } diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/ScrollEventMessage.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/ScrollEventMessage.ts index b11be19d82..a9ee1c2e7d 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/ScrollEventMessage.ts +++ b/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; } diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/PreviewerServerConnection.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/PreviewerServerConnection.ts index d9ace9857f..c0c11898d4 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/PreviewerServerConnection.ts +++ b/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, diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/index.html b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/index.html index 35c17185e8..f2ec4e7fd3 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/index.html +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/index.html @@ -5,7 +5,11 @@ Avalonia XAML previewer web edition - + + +
Loading...
diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/tsconfig.json b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/tsconfig.json index 0324a239cf..8b1f40ed83 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/tsconfig.json +++ b/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, diff --git a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/Models/InputEventTests.ts b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/Models/InputEventTests.ts index e96c4d4731..7ef36f8510 100644 --- a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/Models/InputEventTests.ts +++ b/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}`)