Skip to content

Commit 4b4304f

Browse files
committed
Merge branch 'map-scaling-incorrectly-des-692'
2 parents 625a787 + 4ba956b commit 4b4304f

File tree

6 files changed

+87
-68
lines changed

6 files changed

+87
-68
lines changed

gui/src/main/window-controller.ts

+4
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,10 @@ export default class WindowController {
271271
_display: Display,
272272
changedMetrics: string[],
273273
) => {
274+
if (changedMetrics.includes('scaleFactor')) {
275+
IpcMainEventChannel.window.notifyScaleFactorChange?.();
276+
}
277+
274278
if (changedMetrics.includes('workArea') && this.window?.isVisible()) {
275279
this.onWorkAreaSizeChange();
276280
if (process.platform === 'win32') {

gui/src/renderer/components/Map.tsx

+65-66
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
1+
import { useCallback, useEffect, useMemo, useRef } from 'react';
22
import styled from 'styled-components';
33

44
import { TunnelState } from '../../shared/daemon-rpc-types';
55
import log from '../../shared/logging';
66
import { useAppContext } from '../context';
77
import GlMap, { ConnectionState, Coordinate } from '../lib/3dmap';
8-
import { useCombinedRefs } from '../lib/utilityHooks';
8+
import { useCombinedRefs, useRerenderer } from '../lib/utilityHooks';
99
import { useSelector } from '../redux/store';
1010

1111
// Default to Gothenburg when we don't know the actual location.
@@ -22,8 +22,6 @@ interface MapParams {
2222
connectionState: ConnectionState;
2323
}
2424

25-
type AnimationFrameCallback = (now: number, newParams?: MapParams) => void;
26-
2725
export default function Map() {
2826
const connection = useSelector((state) => state.connection);
2927
const animateMap = useSelector((state) => state.settings.guiSettings.animateMap);
@@ -77,29 +75,47 @@ interface MapInnerProps extends MapParams {
7775
function MapInner(props: MapInnerProps) {
7876
const { getMapData } = useAppContext();
7977

80-
// Callback that should be passed to requestAnimationFrame. This is initialized after the canvas
81-
// has been rendered.
82-
const animationFrameCallback = useRef<AnimationFrameCallback>();
8378
// When location or connection state changes it's stored here until passed to 3dmap
8479
const newParams = useRef<MapParams>();
8580

8681
// This is set to true when rendering should be paused
8782
const pause = useRef<boolean>(false);
8883

84+
const mapRef = useRef<GlMap>();
8985
const canvasRef = useRef<HTMLCanvasElement>();
90-
const [canvasWidth, setCanvasWidth] = useState(window.innerWidth);
86+
const width = applyPixelRatio(canvasRef.current?.clientWidth ?? window.innerWidth);
9187
// This constant is used for the height the first frame that is rendered only.
92-
const [canvasHeight, setCanvasHeight] = useState(493);
88+
const height = applyPixelRatio(canvasRef.current?.clientHeight ?? 493);
9389

94-
const updateCanvasSize = useCallback((canvas: HTMLCanvasElement) => {
95-
const canvasRect = canvas.getBoundingClientRect();
90+
// Hack to rerender when window size changes or when ref is set.
91+
const [onSizeChange, sizeChangeCounter] = useRerenderer();
9692

97-
canvas.width = applyScaleFactor(canvasRect.width);
98-
canvas.height = applyScaleFactor(canvasRect.height);
93+
const render = useCallback(() => requestAnimationFrame(animationFrameCallback), []);
9994

100-
setCanvasWidth(canvasRect.width);
101-
setCanvasHeight(canvasRect.height);
102-
}, []);
95+
const animationFrameCallback = useCallback(
96+
(now: number) => {
97+
now *= 0.001; // convert to seconds
98+
99+
// Propagate location change to the map
100+
if (newParams.current) {
101+
mapRef.current?.setLocation(
102+
newParams.current.location,
103+
newParams.current.connectionState,
104+
now,
105+
props.animate,
106+
);
107+
newParams.current = undefined;
108+
}
109+
110+
mapRef.current?.draw(now);
111+
112+
// Stops rendering if pause is true. This happens when there is no ongoing movements
113+
if (!pause.current) {
114+
render();
115+
}
116+
},
117+
[props.animate],
118+
);
103119

104120
// This is called when the canvas has been rendered the first time and initializes the gl context
105121
// and the map.
@@ -108,42 +124,19 @@ function MapInner(props: MapInnerProps) {
108124
return;
109125
}
110126

111-
updateCanvasSize(canvas);
127+
onSizeChange();
112128

113129
const gl = canvas.getContext('webgl2', { antialias: true })!;
114130

115-
const map = new GlMap(
131+
mapRef.current = new GlMap(
116132
gl,
117133
await getMapData(),
118134
props.location,
119135
props.connectionState,
120136
() => (pause.current = true),
121137
);
122138

123-
// Function to be used when calling requestAnimationFrame
124-
animationFrameCallback.current = (now: number) => {
125-
now *= 0.001; // convert to seconds
126-
127-
// Propagate location change to the map
128-
if (newParams.current) {
129-
map.setLocation(
130-
newParams.current.location,
131-
newParams.current.connectionState,
132-
now,
133-
props.animate,
134-
);
135-
newParams.current = undefined;
136-
}
137-
138-
map.draw(now);
139-
140-
// Stops rendering if pause is true. This happens when there is no ongoing movements
141-
if (!pause.current) {
142-
requestAnimationFrame(animationFrameCallback.current!);
143-
}
144-
};
145-
146-
requestAnimationFrame(animationFrameCallback.current);
139+
render();
147140
}, []);
148141

149142
// Set new params when the location or connection state has changed, and unpause if paused
@@ -155,41 +148,47 @@ function MapInner(props: MapInnerProps) {
155148

156149
if (pause.current) {
157150
pause.current = false;
158-
if (animationFrameCallback.current) {
159-
requestAnimationFrame(animationFrameCallback.current);
160-
}
151+
render();
161152
}
162153
}, [props.location, props.connectionState]);
163154

155+
useEffect(() => {
156+
mapRef.current?.updateViewport();
157+
render();
158+
}, [width, height, sizeChangeCounter]);
159+
164160
// Resize canvas if window size changes
165161
useEffect(() => {
166-
const resizeCallback = () => {
167-
if (canvasRef.current) {
168-
updateCanvasSize(canvasRef.current);
169-
}
170-
};
162+
addEventListener('resize', onSizeChange);
163+
return () => removeEventListener('resize', onSizeChange);
164+
}, []);
171165

172-
addEventListener('resize', resizeCallback);
173-
return () => removeEventListener('resize', resizeCallback);
174-
}, [updateCanvasSize]);
166+
useEffect(() => {
167+
const unsubscribe = window.ipc.window.listenScaleFactorChange(onSizeChange);
168+
return () => unsubscribe();
169+
}, []);
175170

176171
// Log new scale factor if it changes
177-
useEffect(() => log.verbose('Map canvas scale factor:', window.devicePixelRatio), [
178-
window.devicePixelRatio,
179-
]);
172+
useEffect(() => {
173+
log.verbose(`Map canvas scale factor: ${window.devicePixelRatio}, using: ${getPixelRatio()}`);
174+
}, [window.devicePixelRatio]);
180175

181176
const combinedCanvasRef = useCombinedRefs(canvasRef, canvasCallback);
182177

183-
return (
184-
<StyledCanvas
185-
ref={combinedCanvasRef}
186-
width={applyScaleFactor(canvasWidth)}
187-
height={applyScaleFactor(canvasHeight)}
188-
/>
189-
);
178+
return <StyledCanvas ref={combinedCanvasRef} width={width} height={height} />;
179+
}
180+
181+
function getPixelRatio(): number {
182+
let pixelRatio = window.devicePixelRatio;
183+
184+
// Wayland renders non-integer values as the next integer and then scales it back down.
185+
if (window.env.platform === 'linux') {
186+
pixelRatio = Math.ceil(pixelRatio);
187+
}
188+
189+
return pixelRatio;
190190
}
191191

192-
function applyScaleFactor(dimension: number): number {
193-
const scaleFactor = window.devicePixelRatio;
194-
return Math.floor(dimension * scaleFactor);
192+
function applyPixelRatio(dimension: number): number {
193+
return Math.floor(dimension * getPixelRatio());
195194
}

gui/src/renderer/lib/3dmap.ts

+4
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,10 @@ export default class GlMap {
451451
this.zoomAnimations = [];
452452
}
453453

454+
public updateViewport() {
455+
this.gl.viewport(0, 0, this.gl.drawingBufferWidth, this.gl.drawingBufferHeight);
456+
}
457+
454458
// Move the location marker to `newCoordinate` (with state `connectionState`).
455459
// Queues an animation to `newCoordinate` if `animate` is true. Otherwise it moves
456460
// directly to that location.

gui/src/renderer/lib/utilityHooks.ts

+9
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,12 @@ export function useNormalBridgeSettings() {
6969
const bridgeSettings = useSelector((state) => state.settings.bridgeSettings);
7070
return bridgeSettings.normal;
7171
}
72+
73+
// This hook returns a function that can be used to force a rerender of a component, and
74+
// additionally also returns a variable that can be used to trigger effects as a result. This is a
75+
// hack and should be avoided unless there are no better ways.
76+
export function useRerenderer(): [() => void, number] {
77+
const [count, setCount] = useState(0);
78+
const rerender = useCallback(() => setCount((count) => count + 1), []);
79+
return [rerender, count];
80+
}

gui/src/shared/ipc-helpers.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { capitalize } from './string-helpers';
66
type Handler<T, R> = (callback: (arg: T) => R) => void;
77
type Sender<T, R> = (arg: T) => R;
88
type Notifier<T> = ((arg: T) => void) | undefined;
9-
type Listener<T> = (callback: (arg: T) => void) => void;
9+
type Listener<T> = (callback: (arg: T) => void) => () => void;
1010

1111
interface MainToRenderer<T> {
1212
direction: 'main-to-renderer';
@@ -154,7 +154,9 @@ export function notifyRenderer<T>(): MainToRenderer<T> {
154154
direction: 'main-to-renderer',
155155
send: notifyRendererImpl,
156156
receive: (event, ipcRenderer) => (fn: (value: T) => void) => {
157-
ipcRenderer.on(event, (_event, newState: T) => fn(newState));
157+
const listener = (_event: unknown, newState: T) => fn(newState);
158+
ipcRenderer.on(event, listener);
159+
return () => ipcRenderer.off(event, listener);
158160
},
159161
};
160162
}

gui/src/shared/ipc-schema.ts

+1
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ export const ipcSchema = {
128128
shape: notifyRenderer<IWindowShapeParameters>(),
129129
focus: notifyRenderer<boolean>(),
130130
macOsScrollbarVisibility: notifyRenderer<MacOsScrollbarVisibility>(),
131+
scaleFactorChange: notifyRenderer<void>(),
131132
},
132133
navigation: {
133134
reset: notifyRenderer<void>(),

0 commit comments

Comments
 (0)