1
- import { useCallback , useEffect , useMemo , useRef , useState } from 'react' ;
1
+ import { useCallback , useEffect , useMemo , useRef } from 'react' ;
2
2
import styled from 'styled-components' ;
3
3
4
4
import { TunnelState } from '../../shared/daemon-rpc-types' ;
5
5
import log from '../../shared/logging' ;
6
6
import { useAppContext } from '../context' ;
7
7
import GlMap , { ConnectionState , Coordinate } from '../lib/3dmap' ;
8
- import { useCombinedRefs } from '../lib/utilityHooks' ;
8
+ import { useCombinedRefs , useRerenderer } from '../lib/utilityHooks' ;
9
9
import { useSelector } from '../redux/store' ;
10
10
11
11
// Default to Gothenburg when we don't know the actual location.
@@ -22,8 +22,6 @@ interface MapParams {
22
22
connectionState : ConnectionState ;
23
23
}
24
24
25
- type AnimationFrameCallback = ( now : number , newParams ?: MapParams ) => void ;
26
-
27
25
export default function Map ( ) {
28
26
const connection = useSelector ( ( state ) => state . connection ) ;
29
27
const animateMap = useSelector ( ( state ) => state . settings . guiSettings . animateMap ) ;
@@ -77,29 +75,47 @@ interface MapInnerProps extends MapParams {
77
75
function MapInner ( props : MapInnerProps ) {
78
76
const { getMapData } = useAppContext ( ) ;
79
77
80
- // Callback that should be passed to requestAnimationFrame. This is initialized after the canvas
81
- // has been rendered.
82
- const animationFrameCallback = useRef < AnimationFrameCallback > ( ) ;
83
78
// When location or connection state changes it's stored here until passed to 3dmap
84
79
const newParams = useRef < MapParams > ( ) ;
85
80
86
81
// This is set to true when rendering should be paused
87
82
const pause = useRef < boolean > ( false ) ;
88
83
84
+ const mapRef = useRef < GlMap > ( ) ;
89
85
const canvasRef = useRef < HTMLCanvasElement > ( ) ;
90
- const [ canvasWidth , setCanvasWidth ] = useState ( window . innerWidth ) ;
86
+ const width = applyPixelRatio ( canvasRef . current ?. clientWidth ?? window . innerWidth ) ;
91
87
// 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 ) ;
93
89
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 ( ) ;
96
92
97
- canvas . width = applyScaleFactor ( canvasRect . width ) ;
98
- canvas . height = applyScaleFactor ( canvasRect . height ) ;
93
+ const render = useCallback ( ( ) => requestAnimationFrame ( animationFrameCallback ) , [ ] ) ;
99
94
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
+ ) ;
103
119
104
120
// This is called when the canvas has been rendered the first time and initializes the gl context
105
121
// and the map.
@@ -108,42 +124,19 @@ function MapInner(props: MapInnerProps) {
108
124
return ;
109
125
}
110
126
111
- updateCanvasSize ( canvas ) ;
127
+ onSizeChange ( ) ;
112
128
113
129
const gl = canvas . getContext ( 'webgl2' , { antialias : true } ) ! ;
114
130
115
- const map = new GlMap (
131
+ mapRef . current = new GlMap (
116
132
gl ,
117
133
await getMapData ( ) ,
118
134
props . location ,
119
135
props . connectionState ,
120
136
( ) => ( pause . current = true ) ,
121
137
) ;
122
138
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 ( ) ;
147
140
} , [ ] ) ;
148
141
149
142
// Set new params when the location or connection state has changed, and unpause if paused
@@ -155,41 +148,47 @@ function MapInner(props: MapInnerProps) {
155
148
156
149
if ( pause . current ) {
157
150
pause . current = false ;
158
- if ( animationFrameCallback . current ) {
159
- requestAnimationFrame ( animationFrameCallback . current ) ;
160
- }
151
+ render ( ) ;
161
152
}
162
153
} , [ props . location , props . connectionState ] ) ;
163
154
155
+ useEffect ( ( ) => {
156
+ mapRef . current ?. updateViewport ( ) ;
157
+ render ( ) ;
158
+ } , [ width , height , sizeChangeCounter ] ) ;
159
+
164
160
// Resize canvas if window size changes
165
161
useEffect ( ( ) => {
166
- const resizeCallback = ( ) => {
167
- if ( canvasRef . current ) {
168
- updateCanvasSize ( canvasRef . current ) ;
169
- }
170
- } ;
162
+ addEventListener ( 'resize' , onSizeChange ) ;
163
+ return ( ) => removeEventListener ( 'resize' , onSizeChange ) ;
164
+ } , [ ] ) ;
171
165
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
+ } , [ ] ) ;
175
170
176
171
// 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 ] ) ;
180
175
181
176
const combinedCanvasRef = useCombinedRefs ( canvasRef , canvasCallback ) ;
182
177
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 ;
190
190
}
191
191
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 ( ) ) ;
195
194
}
0 commit comments