fixed window flashing bug
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
"""The main window must never be hidden while the operator switches sensor type.
|
||||
|
||||
The 3D pane is built on first use, so on a first run — the pane starts on a box, which
|
||||
has no 3D scene — its ``QOpenGLWidget`` is created later, while the window is already on
|
||||
screen. Creating the first *native* child of a visible window makes Qt re-create that
|
||||
window, and Qt does that by hiding the window and showing it again: the whole
|
||||
application disappears for a few hundred milliseconds and whatever popup or dialog was
|
||||
open is dismissed with it. Switching the sensor to ``finger`` in the sensor dialog is
|
||||
exactly that moment.
|
||||
|
||||
These are the acceptance conditions: the window never hides and never takes a close
|
||||
event, and the dialog and popup that were open are not silently dismissed.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from PySide6.QtCore import QObject
|
||||
from PySide6.QtWidgets import QWidget
|
||||
|
||||
from orisys_studio.scene3d.view import gl_available
|
||||
from orisys_studio.ui.mesh3d_panel import Mesh3dPanel
|
||||
|
||||
#: The window's own lifecycle, as the operator experiences it: a hide is the window
|
||||
#: vanishing, a close is the application going away, and neither may happen here.
|
||||
WATCHED = ("hideEvent", "closeEvent")
|
||||
|
||||
|
||||
class _LifecycleWatcher(QObject):
|
||||
"""Record every hide/close the window receives, in order."""
|
||||
|
||||
def __init__(self, window) -> None:
|
||||
super().__init__(window)
|
||||
self.seen: list[str] = []
|
||||
for name in WATCHED:
|
||||
setattr(self, f"_original_{name}", getattr(window, name))
|
||||
window.hideEvent = self.hideEvent
|
||||
window.closeEvent = self.closeEvent
|
||||
|
||||
def hideEvent(self, event) -> None:
|
||||
self.seen.append("hideEvent")
|
||||
self._original_hideEvent(event)
|
||||
|
||||
def closeEvent(self, event) -> None:
|
||||
self.seen.append("closeEvent")
|
||||
self._original_closeEvent(event)
|
||||
|
||||
|
||||
def _pick(window, sensor_type: str) -> None:
|
||||
"""Do what the operator does: choose ``sensor_type`` in the rail's sensor dialog."""
|
||||
combo = window.sensor_picker.type_combo
|
||||
combo.setCurrentIndex(combo.findData(sensor_type))
|
||||
|
||||
|
||||
def test_reserving_the_native_handle_costs_nothing_at_construction(qt_app, make_window):
|
||||
"""The handle is reserved, and reserving it does not build the pane.
|
||||
|
||||
``winId()`` on the handle forces the native window to exist, which is what makes Qt
|
||||
reuse this top-level window later instead of re-creating it; and reserving it must
|
||||
still not import GL, read a scene or create a context. The window platforms give a
|
||||
native window a minimum size, so the reservation is checked by *where* it is and that
|
||||
it never shows, not by a pixel size.
|
||||
"""
|
||||
window = make_window(sensor_type="box")
|
||||
try:
|
||||
panel = window.mesh3d_panel
|
||||
assert isinstance(panel, Mesh3dPanel)
|
||||
assert panel.view is None, "the pane was built before anything asked for it"
|
||||
assert panel.scene_path() is None
|
||||
handle = panel.findChild(QWidget, "mesh3dNativeHandle")
|
||||
assert handle is not None, "the pane did not reserve a native handle"
|
||||
assert handle.internalWinId() != 0, (
|
||||
"the handle exists but Qt has not made it native, which is the whole point"
|
||||
)
|
||||
assert handle.isVisible() is False, (
|
||||
"the reserved handle is a handle, not something to show"
|
||||
)
|
||||
assert handle.x() == 0 and handle.y() == 0, (
|
||||
"the reserved handle must not take room from the pane"
|
||||
)
|
||||
|
||||
window.resize(1440, 880)
|
||||
window.show()
|
||||
qt_app.processEvents()
|
||||
assert handle.isVisible() is False, "the reserved handle showed up on screen"
|
||||
assert panel.width() > 1, "the reservation took the pane's own space"
|
||||
assert panel.view is None, "showing the window built the pane by itself"
|
||||
finally:
|
||||
window.close()
|
||||
|
||||
|
||||
def test_switching_to_a_finger_never_hides_the_window_or_dismisses_the_dialog(
|
||||
qt_app, make_window
|
||||
):
|
||||
available, reason = gl_available()
|
||||
if not available:
|
||||
import pytest
|
||||
|
||||
pytest.skip(f"no 3D widget on this machine: {reason}")
|
||||
|
||||
window = make_window(sensor_type="box", source_mode="camera")
|
||||
window.resize(1440, 880)
|
||||
window.show()
|
||||
qt_app.processEvents()
|
||||
|
||||
# The first run's distinguishing state: nothing has built the pane yet.
|
||||
assert window.mesh3d_panel.view is None
|
||||
|
||||
window._open_sensor_dialog()
|
||||
dialog = window.sensor_dialog
|
||||
assert dialog is not None and dialog.isVisible()
|
||||
|
||||
# The popup an operator's click leaves open. Qt dismisses both of these along with
|
||||
# the window, so both are the thing this guards.
|
||||
combo = window.sensor_picker.type_combo
|
||||
combo.showPopup()
|
||||
qt_app.processEvents()
|
||||
popup = combo.view()
|
||||
assert popup is not None and popup.isVisible()
|
||||
|
||||
watcher = _LifecycleWatcher(window)
|
||||
try:
|
||||
_pick(window, "finger")
|
||||
qt_app.processEvents()
|
||||
|
||||
assert watcher.seen == [], (
|
||||
"the window was hidden/closed by the sensor switch: " + repr(watcher.seen)
|
||||
)
|
||||
assert window.isVisible(), "the window left the screen during the switch"
|
||||
assert dialog.isVisible(), "the sensor dialog was dismissed by the switch"
|
||||
assert popup.isVisible(), "the popup was dismissed along with the window"
|
||||
assert window.mesh3d_panel.view is not None, (
|
||||
"no 3D pane was built, so the case this guards was never reached"
|
||||
)
|
||||
assert window.sensor_picker.sensor_type() == "finger"
|
||||
finally:
|
||||
dialog.close()
|
||||
window.close()
|
||||
|
||||
|
||||
def test_the_switch_is_quiet_on_later_runs_too(qt_app, make_window):
|
||||
"""The other direction: the pane already exists, and back-and-forth stays quiet.
|
||||
|
||||
This is the second-run shape (a saved ``finger`` preference builds the pane at
|
||||
start-up). A layout change that hid the window would be a bug on any run, not only
|
||||
the first.
|
||||
"""
|
||||
available, reason = gl_available()
|
||||
if not available:
|
||||
import pytest
|
||||
|
||||
pytest.skip(f"no 3D widget on this machine: {reason}")
|
||||
|
||||
window = make_window(sensor_type="finger", source_mode="camera")
|
||||
window.resize(1440, 880)
|
||||
window.show()
|
||||
qt_app.processEvents()
|
||||
assert window.mesh3d_panel.view is not None, (
|
||||
"a saved finger preference must build the pane before the window is shown"
|
||||
)
|
||||
|
||||
watcher = _LifecycleWatcher(window)
|
||||
try:
|
||||
_pick(window, "box")
|
||||
qt_app.processEvents()
|
||||
_pick(window, "finger")
|
||||
qt_app.processEvents()
|
||||
assert watcher.seen == [], repr(watcher.seen)
|
||||
assert window.isVisible()
|
||||
finally:
|
||||
window.close()
|
||||
Reference in New Issue
Block a user