@@ -150,20 +150,47 @@ def init_ui(self):
150150 display_layout = QtWidgets .QVBoxLayout (display_group )
151151
152152 # Point size control
153- display_layout .addWidget (QtWidgets .QLabel ("Point size (px):" ))
153+ point_size_layout = QtWidgets .QHBoxLayout ()
154+ point_size_layout .addWidget (QtWidgets .QLabel ("Point size:" ))
155+
154156 self .point_size_spin = QtWidgets .QSpinBox ()
155157 self .point_size_spin .setRange (1 , 100 )
156158 self .point_size_spin .setValue (self .points_size )
157- self .point_size_spin .valueChanged .connect (self .update_point_size )
158- display_layout .addWidget (self .point_size_spin )
159+ self .point_size_spin .setSuffix ("px" )
160+ self .point_size_spin .setFixedWidth (80 )
161+ self .point_size_spin .valueChanged .connect (self .update_point_size_from_spinbox )
162+ point_size_layout .addWidget (self .point_size_spin )
163+
164+ point_size_layout .addStretch () # Push everything to the left
165+ display_layout .addLayout (point_size_layout )
166+
167+ self .point_size_slider = QtWidgets .QSlider (QtCore .Qt .Orientation .Horizontal )
168+ self .point_size_slider .setRange (1 , 20 )
169+ self .point_size_slider .setValue (min (20 , self .points_size ))
170+ self .point_size_slider .valueChanged .connect (self .update_point_size_from_slider )
171+ display_layout .addWidget (self .point_size_slider )
159172
160173 # Magnification control
161- display_layout .addWidget (QtWidgets .QLabel ("Magnify:" ))
162- self .mag_spin = QtWidgets .QSpinBox ()
163- self .mag_spin .setRange (1 , 10000 )
174+ mag_layout = QtWidgets .QHBoxLayout ()
175+ mag_layout .addWidget (QtWidgets .QLabel ("Magnify:" ))
176+
177+ self .mag_spin = QtWidgets .QDoubleSpinBox ()
178+ self .mag_spin .setRange (0.01 , 999999 ) # No practical upper limit
179+ self .mag_spin .setSingleStep (0.01 )
164180 self .mag_spin .setValue (self .magnification )
165- self .mag_spin .valueChanged .connect (self .update_frame )
166- display_layout .addWidget (self .mag_spin )
181+ self .mag_spin .setSuffix ("x" )
182+ self .mag_spin .setFixedWidth (80 )
183+ self .mag_spin .valueChanged .connect (self .update_mag_from_spinbox )
184+ mag_layout .addWidget (self .mag_spin )
185+
186+ mag_layout .addStretch () # Push everything to the left
187+ display_layout .addLayout (mag_layout )
188+
189+ self .mag_slider = QtWidgets .QSlider (QtCore .Qt .Orientation .Horizontal )
190+ self .mag_slider .setRange (1 , 1000 ) # 0.1x to 10x (in percent: 10% to 1000%)
191+ self .mag_slider .setValue (int (self .magnification * 100 ))
192+ self .mag_slider .valueChanged .connect (self .update_mag_from_slider )
193+ display_layout .addWidget (self .mag_slider )
167194
168195 # Show arrows checkbox
169196 self .arrows_checkbox = QtWidgets .QCheckBox ("Show arrows" )
@@ -177,21 +204,25 @@ def init_ui(self):
177204 playback_layout = QtWidgets .QVBoxLayout (playback_group )
178205
179206 # FPS control
180- self .fps_label = QtWidgets .QLabel (f"FPS: { self .fps } " )
181- playback_layout .addWidget (self .fps_label )
207+ fps_layout = QtWidgets .QHBoxLayout ()
208+ fps_layout .addWidget (QtWidgets .QLabel ("FPS:" ))
209+
210+ self .fps_spin = QtWidgets .QSpinBox ()
211+ self .fps_spin .setRange (1 , 240 )
212+ self .fps_spin .setValue (self .fps )
213+ self .fps_spin .setFixedWidth (80 )
214+ self .fps_spin .valueChanged .connect (self .update_fps_from_spinbox )
215+ fps_layout .addWidget (self .fps_spin )
216+
217+ fps_layout .addStretch () # Push everything to the left
218+ playback_layout .addLayout (fps_layout )
182219
183220 self .fps_slider = QtWidgets .QSlider (QtCore .Qt .Orientation .Horizontal )
184221 self .fps_slider .setRange (1 , 240 )
185222 self .fps_slider .setValue (self .fps )
186223 self .fps_slider .valueChanged .connect (self .update_fps_from_slider )
187224 playback_layout .addWidget (self .fps_slider )
188225
189- self .fps_spin = QtWidgets .QSpinBox ()
190- self .fps_spin .setRange (1 , 240 )
191- self .fps_spin .setValue (self .fps )
192- self .fps_spin .valueChanged .connect (self .update_fps_from_spinbox )
193- playback_layout .addWidget (self .fps_spin )
194-
195226 self .control_layout .addWidget (playback_group )
196227
197228 # Export controls group
@@ -285,7 +316,7 @@ def init_ui(self):
285316 # === Finalize ===
286317 self .setCentralWidget (central_widget )
287318 self .setWindowTitle ("Displacement Viewer" )
288- self .resize (800 , 600 )
319+ self .resize (1200 , 600 )
289320
290321 def toggle_playback (self ):
291322 if self .timer .isActive ():
@@ -311,21 +342,70 @@ def on_fps_change(self):
311342
312343 def update_fps_from_slider (self , value ):
313344 self .fps = value
314- self .fps_label .setText (f"FPS: { value } " )
315345 self .fps_spin .blockSignals (True ) # Prevent recursive updates
316346 self .fps_spin .setValue (value )
317347 self .fps_spin .blockSignals (False )
318348 self .on_fps_change ()
319349
320350 def update_fps_from_spinbox (self , value ):
321351 self .fps = value
322- self .fps_label .setText (f"FPS: { value } " )
323352 self .fps_slider .blockSignals (True ) # Prevent recursive updates
324353 self .fps_slider .setValue (value )
325354 self .fps_slider .blockSignals (False )
326355 self .on_fps_change ()
327356
357+ def update_mag_from_slider (self , value ):
358+ # Convert slider value (1-1000) to magnification (0.01-10.0)
359+ magnification = value / 100.0
360+ self .magnification = magnification
361+
362+ # Update spinbox without triggering its signal
363+ self .mag_spin .blockSignals (True )
364+ self .mag_spin .setValue (magnification )
365+ self .mag_spin .blockSignals (False )
366+
367+ self .update_frame ()
368+
369+ def update_mag_from_spinbox (self , value ):
370+ # Convert spinbox value to magnification
371+ magnification = value
372+ self .magnification = magnification
373+
374+ # Update slider, clamping to its range and converting to int
375+ slider_value = int (max (1 , min (1000 , value * 100 )))
376+ self .mag_slider .blockSignals (True )
377+ self .mag_slider .setValue (slider_value )
378+ self .mag_slider .blockSignals (False )
379+
380+ self .update_frame ()
381+
382+ def update_point_size_from_slider (self , value ):
383+ # Update the internal point size
384+ self .points_size = value
385+
386+ # Update spinbox without triggering its signal
387+ self .point_size_spin .blockSignals (True )
388+ self .point_size_spin .setValue (value )
389+ self .point_size_spin .blockSignals (False )
390+
391+ # Update the actual display
392+ self .scatter .setSize (value )
393+
394+ def update_point_size_from_spinbox (self , value ):
395+ # Update the internal point size
396+ self .points_size = value
397+
398+ # Update slider, clamping to its range
399+ slider_value = min (20 , max (1 , value ))
400+ self .point_size_slider .blockSignals (True )
401+ self .point_size_slider .setValue (slider_value )
402+ self .point_size_slider .blockSignals (False )
403+
404+ # Update the actual display
405+ self .scatter .setSize (value )
406+
328407 def update_point_size (self ):
408+ # Keep this method for backward compatibility if needed
329409 size = self .point_size_spin .value ()
330410 self .scatter .setSize (size )
331411
@@ -344,7 +424,7 @@ def on_slider(self, val):
344424 self .update_frame ()
345425
346426 def update_frame (self ):
347- scale = self .mag_spin . value ()
427+ scale = self .magnification
348428
349429 if self .is_mode_shape :
350430 frame = self .video [0 ]
0 commit comments