@@ -166,6 +166,86 @@ export const TraceViewer = ({traceData,width, height, onSelect}) => {
166166 setMouseDownLocation ( e . evt . layerX )
167167 }
168168 }
169+ const [ lastTouchX , setLastTouchX ] = useState ( null )
170+
171+ const handleTouchStart = ( e ) => {
172+ if ( e . evt . touches . length === 1 ) {
173+ setLastTouchX ( e . evt . touches [ 0 ] . clientX ) ;
174+ }
175+ }
176+ const [ lastPinchDistance , setLastPinchDistance ] = useState ( null ) ;
177+ const handleTouchMove = ( e ) => {
178+ const touches = e . evt . touches ;
179+
180+ if ( touches . length === 2 ) {
181+ e . evt . preventDefault ( ) ; // Prevent page scroll
182+
183+ const dx = touches [ 0 ] . clientX - touches [ 1 ] . clientX ;
184+ const dy = touches [ 0 ] . clientY - touches [ 1 ] . clientY ;
185+ const distance = Math . sqrt ( dx * dx + dy * dy ) ;
186+
187+ const centerX = ( touches [ 0 ] . clientX + touches [ 1 ] . clientX ) / 2 ;
188+ const layerX = centerX - offsetX ;
189+ const timePosition = layerX / scaleX + timeRange [ 0 ] ;
190+
191+ if ( lastPinchDistance ) {
192+ const scaleFactor = distance / lastPinchDistance ;
193+ let newScaleX = scaleX * scaleFactor ;
194+
195+ if ( newScaleX < initialScaleX ) {
196+ newScaleX = initialScaleX ;
197+ setScaleX ( newScaleX ) ;
198+ setTimeRange ( [ 0 , maxTime ] ) ;
199+ } else {
200+ const newPosition = ( timePosition - timeRange [ 0 ] ) * newScaleX ;
201+ const diff = layerX - newPosition ;
202+ let newTimeStart = timeRange [ 0 ] - diff / newScaleX ;
203+ let newTimeEnd = newTimeStart + dataWidth / newScaleX ;
204+
205+ if ( newTimeEnd > maxTime ) {
206+ newTimeStart -= newTimeEnd - maxTime ;
207+ newTimeEnd = maxTime ;
208+ }
209+
210+ if ( newTimeStart < 0 ) {
211+ newTimeStart = 0 ;
212+ newTimeEnd = dataWidth / newScaleX ;
213+ }
214+
215+ setScaleX ( newScaleX ) ;
216+ setTimeRange ( [ newTimeStart , newTimeEnd ] ) ;
217+ }
218+ }
219+
220+ setLastPinchDistance ( distance ) ;
221+ } else if ( touches . length === 1 && lastTouchX !== null ) {
222+ // one-finger pan
223+ const currentX = touches [ 0 ] . clientX ;
224+ const deltaX = currentX - lastTouchX ;
225+
226+ let newTimeStart = timeRange [ 0 ] - deltaX / scaleX ;
227+ let newTimeEnd = newTimeStart + dataWidth / scaleX ;
228+
229+ if ( newTimeStart < 0 ) {
230+ newTimeStart = 0 ;
231+ newTimeEnd = dataWidth / scaleX ;
232+ }
233+ if ( newTimeEnd > maxTime ) {
234+ newTimeStart -= newTimeEnd - maxTime ;
235+ newTimeEnd = maxTime ;
236+ }
237+
238+ setTimeRange ( [ newTimeStart , newTimeEnd ] ) ;
239+ setLastTouchX ( currentX ) ;
240+ }
241+ }
242+
243+
244+ const handleTouchEnd = ( ) => {
245+ setLastTouchX ( null ) ;
246+ setLastPinchDistance ( null ) ;
247+ }
248+
169249 const [ rows , setRows ] = useState ( [ ] ) ;
170250
171251
@@ -216,10 +296,25 @@ export const TraceViewer = ({traceData,width, height, onSelect}) => {
216296 //}, []);
217297 return (
218298 < div >
219- < div style = { { maxHeight : height - 60 , overflowY : "auto" } } title = { "CTRL+Scroll for Zooming, Drag and Drop for Moving" } >
220- < Stage width = { canvasWidth } height = { window . innerHeight }
221- onWheel = { ( e ) => handleScroll ( e ) } onMouseDown = { handleMouseDown } onMouseUp = { handleMouseUp }
222- onMouseMove = { handleMouseMove } >
299+ < div
300+ style = { {
301+ maxHeight : height - 60 ,
302+ overflowY : "auto" ,
303+ //touchAction: "none", // prevent browser gestures like zoom
304+ WebkitOverflowScrolling : "touch" // keep native scrolling on parent
305+ } }
306+ title = { "CTRL+Scroll for Zooming, Drag and Drop for Moving" }
307+ > < Stage
308+ width = { canvasWidth }
309+ height = { window . innerHeight }
310+ onWheel = { ( e ) => handleScroll ( e ) }
311+ onMouseDown = { handleMouseDown }
312+ onMouseUp = { handleMouseUp }
313+ onMouseMove = { handleMouseMove }
314+ onTouchStart = { handleTouchStart }
315+ onTouchMove = { handleTouchMove }
316+ onTouchEnd = { handleTouchEnd }
317+ >
223318 < Layer >
224319 {
225320 threads . map ( ( t , i ) => < Group >
@@ -248,14 +343,14 @@ export const TraceViewer = ({traceData,width, height, onSelect}) => {
248343 var correctedDuration = d . start < timeRange [ 0 ] ? duration - ( timeRange [ 0 ] - d . start ) : duration
249344 correctedDuration = d . start + duration > timeRange [ 1 ] ? duration - ( d . start + duration - timeRange [ 1 ] ) : correctedDuration
250345 if ( duration == 0 ) {
251- return < Circle x = { correctedStart * scaleX + offsetX } y = { d . row * rowOffset + offsetY } radius = { 2 } fill = { "black" } onClick = { ( ) => selectEvent ( d ) } > </ Circle >
346+ return < Circle x = { correctedStart * scaleX + offsetX } y = { d . row * rowOffset + offsetY } radius = { 2 } fill = { "black" } onClick = { ( ) => selectEvent ( d ) } onTap = { ( ) => selectEvent ( d ) } > </ Circle >
252347 } else {
253348 let extraText = formatExtraText ( d )
254- return ( < Group onClick = { ( ) => selectEvent ( d ) } >
349+ return ( < Group onClick = { ( ) => selectEvent ( d ) } onTap = { ( ) => selectEvent ( d ) } >
255350 < Rect x = { offsetX + correctedStart * scaleX } y = { offsetY + d . row * rowOffset } height = { 15 }
256351 width = { correctedDuration * scaleX }
257352 fill = { d . category === "Ignore" ? "white" :categoryColors [ d . category + "::" + d . name ] } stroke = { "gray" } strokeWidth = { 0.4 }
258- onClick = { ( ) => { } } > </ Rect >
353+ onClick = { ( ) => { } } onTap = { ( ) => { } } > </ Rect >
259354
260355 < Text x = { offsetX + correctedStart * scaleX } y = { offsetY + d . row * rowOffset }
261356 text = { `${ d . name } (${ extraText } )` }
0 commit comments