fix: preserve TUN switch animations

Keep user-initiated switch animation running when the shared settings controller echoes the committed TUN state, and animate the peer surface without emitting a recursive request. Preserve snap synchronization for initialization and rollback, with real Qt coverage for both directions and rapid toggles.

Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
Loren Eteval
2026-08-30 17:29:31 +08:00
parent be40bd1771
commit ec6d01e4b1
4 changed files with 65 additions and 4 deletions
+17
View File
@@ -148,6 +148,23 @@ class AppQSwitch(Mixins.ThemeAware, QCheckBox):
del blocker
def syncCheckedAnimated(self, checked: bool):
"""Animate changed external state without emitting another request."""
checked = bool(checked)
if self.isChecked() == checked:
# A user toggle already started the canonical animation before its
# committed state was broadcast back through the shared authority.
return
blocker = QtCore.QSignalBlocker(self)
self.setChecked(checked)
del blocker
self._animateToggle(checked)
def hitButton(self, position: QtCore.QPoint) -> bool:
"""Make the complete custom-painted switch track interactive."""
return self.rect().contains(position)
+3 -1
View File
@@ -825,7 +825,9 @@ class HomePage(Mixins.QTranslatable, QMainWindow):
AppSettingsController().systemProxyModeChanged.connect(
self._syncSystemProxyMode
)
AppSettingsController().tunModeChanged.connect(self.tunModeSwitch.syncChecked)
AppSettingsController().tunModeChanged.connect(
self.tunModeSwitch.syncCheckedAnimated
)
AppConnectionController().interactionEnabledChanged.connect(
self.setConnectionControlsEnabled
+1 -1
View File
@@ -661,7 +661,7 @@ class SettingsPage(Mixins.QTranslatable, QMainWindow):
self.tunModeCard.checkBox.setEnabled(self._tunModeAvailable)
AppSettingsController().tunModeChanged.connect(
self.tunModeCard.checkBox.syncChecked
self.tunModeCard.checkBox.syncCheckedAnimated
)
(
+44 -2
View File
@@ -25,7 +25,7 @@ from Furious.Frozenlib import AppBuiltinProxyMode, AppSettings
from Furious.Models import CoreConfiguration, ServerProfile
from Furious.Plugins.API import RoutingOption
from Furious.Repository import Storage, SubscriptionGroup
from Furious.Qt import AppQDialog
from Furious.Qt import AppQDialog, AppQSwitch
from Furious.Widget.RoutingSelector import RoutingSelector
from Furious.Widget.ServerTableView import ServerTableView
from Furious.Window.HomePage import HomePage
@@ -690,7 +690,9 @@ class SharedSettingsQtWorkflowTest(unittest.TestCase):
)
settingsController.systemProxyModeChanged.connect(proxyCard.sync)
settingsController.tunModeChanged.connect(tunCard.checkBox.syncChecked)
settingsController.tunModeChanged.connect(
tunCard.checkBox.syncCheckedAnimated
)
settingsLayout.addWidget(proxyCard)
settingsLayout.addWidget(tunCard)
@@ -740,10 +742,20 @@ class SharedSettingsQtWorkflowTest(unittest.TestCase):
QtCore.Qt.MouseButton.LeftButton,
pos=home.tunModeSwitch.rect().center(),
)
QTest.qWait(40)
for switch in (home.tunModeSwitch, tunCard.checkBox):
self.assertGreater(switch.thumbPosition, 0.0)
self.assertLess(switch.thumbPosition, 1.0)
QTest.qWait(AppQSwitch.AnimationDuration)
processQtEvents()
self.assertTrue(home.tunModeSwitch.isChecked())
self.assertTrue(tunCard.checkBox.isChecked())
self.assertEqual(home.tunModeSwitch.thumbPosition, 1.0)
self.assertEqual(tunCard.checkBox.thumbPosition, 1.0)
self.assertEqual(tunStates, [True])
QTest.mouseClick(
@@ -751,12 +763,42 @@ class SharedSettingsQtWorkflowTest(unittest.TestCase):
QtCore.Qt.MouseButton.LeftButton,
pos=tunCard.checkBox.rect().center(),
)
QTest.qWait(40)
for switch in (home.tunModeSwitch, tunCard.checkBox):
self.assertGreater(switch.thumbPosition, 0.0)
self.assertLess(switch.thumbPosition, 1.0)
QTest.qWait(AppQSwitch.AnimationDuration)
processQtEvents()
self.assertFalse(home.tunModeSwitch.isChecked())
self.assertFalse(tunCard.checkBox.isChecked())
self.assertEqual(home.tunModeSwitch.thumbPosition, 0.0)
self.assertEqual(tunCard.checkBox.thumbPosition, 0.0)
self.assertEqual(tunStates, [True, False])
QTest.mouseClick(
home.tunModeSwitch,
QtCore.Qt.MouseButton.LeftButton,
pos=home.tunModeSwitch.rect().center(),
)
QTest.qWait(20)
QTest.mouseClick(
home.tunModeSwitch,
QtCore.Qt.MouseButton.LeftButton,
pos=home.tunModeSwitch.rect().center(),
)
QTest.qWait(AppQSwitch.AnimationDuration + 40)
processQtEvents()
self.assertFalse(home.tunModeSwitch.isChecked())
self.assertFalse(tunCard.checkBox.isChecked())
self.assertEqual(home.tunModeSwitch.thumbPosition, 0.0)
self.assertEqual(tunCard.checkBox.thumbPosition, 0.0)
self.assertEqual(tunStates, [True, False, True, False])
connectionController.setInteractionEnabled(False)
processQtEvents()