收起复用与完成态分析横幅。
来源栏已显示结果状态,横幅约 1.5 秒后只隐藏控件并保留 state()。已用 apps/studio/tests/test_analysis_progress.py 验证。
This commit is contained in:
@@ -28,7 +28,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from orisys_app_shared.i18n import t
|
from orisys_app_shared.i18n import t
|
||||||
from orisys_app_shared.theme import color
|
from orisys_app_shared.theme import color
|
||||||
from PySide6.QtCore import Qt, Signal
|
from PySide6.QtCore import Qt, QTimer, Signal
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QFrame,
|
QFrame,
|
||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
@@ -71,6 +71,10 @@ _STATE_TITLE = {
|
|||||||
#: The progress bar's thousandths (Qt's own percentage unit, 0–1000).
|
#: The progress bar's thousandths (Qt's own percentage unit, 0–1000).
|
||||||
_FULL = 1000
|
_FULL = 1000
|
||||||
|
|
||||||
|
#: How long a reused / complete banner stays on screen before collapsing. The rail
|
||||||
|
#: already names the result state, so the banner must not keep stealing height.
|
||||||
|
_COLLAPSE_MS = 1500
|
||||||
|
|
||||||
|
|
||||||
class AnalysisProgress(QFrame):
|
class AnalysisProgress(QFrame):
|
||||||
"""Shows one analysis pass: how far, which segment, and how to stop it."""
|
"""Shows one analysis pass: how far, which segment, and how to stop it."""
|
||||||
@@ -104,6 +108,9 @@ class AnalysisProgress(QFrame):
|
|||||||
self.cancel_button.setObjectName("analysisCancel")
|
self.cancel_button.setObjectName("analysisCancel")
|
||||||
self.cancel_button.setToolTip(t("analysis.cancel_tip"))
|
self.cancel_button.setToolTip(t("analysis.cancel_tip"))
|
||||||
self.cancel_button.clicked.connect(self._on_cancel_clicked)
|
self.cancel_button.clicked.connect(self._on_cancel_clicked)
|
||||||
|
self._collapse_timer = QTimer(self)
|
||||||
|
self._collapse_timer.setSingleShot(True)
|
||||||
|
self._collapse_timer.timeout.connect(self._collapse_success_banner)
|
||||||
|
|
||||||
footer = QHBoxLayout()
|
footer = QHBoxLayout()
|
||||||
footer.setContentsMargins(0, 0, 0, 0)
|
footer.setContentsMargins(0, 0, 0, 0)
|
||||||
@@ -135,6 +142,7 @@ class AnalysisProgress(QFrame):
|
|||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
"""A pass began: show the surface empty and cancellable."""
|
"""A pass began: show the surface empty and cancellable."""
|
||||||
|
self._cancel_collapse()
|
||||||
self._state = RUNNING
|
self._state = RUNNING
|
||||||
self._cancelling = False
|
self._cancelling = False
|
||||||
self._detail = ""
|
self._detail = ""
|
||||||
@@ -186,6 +194,7 @@ class AnalysisProgress(QFrame):
|
|||||||
self._detail = ""
|
self._detail = ""
|
||||||
self.setVisible(True)
|
self.setVisible(True)
|
||||||
self._refresh()
|
self._refresh()
|
||||||
|
self._schedule_collapse()
|
||||||
|
|
||||||
def complete(self, *, frames: int = 0, elapsed_s: float = 0.0) -> None:
|
def complete(self, *, frames: int = 0, elapsed_s: float = 0.0) -> None:
|
||||||
"""The pass finished and the result may be played from."""
|
"""The pass finished and the result may be played from."""
|
||||||
@@ -196,9 +205,11 @@ class AnalysisProgress(QFrame):
|
|||||||
self._detail = t("analysis.complete", frames=int(frames), seconds=float(elapsed_s))
|
self._detail = t("analysis.complete", frames=int(frames), seconds=float(elapsed_s))
|
||||||
self.setVisible(True)
|
self.setVisible(True)
|
||||||
self._refresh()
|
self._refresh()
|
||||||
|
self._schedule_collapse()
|
||||||
|
|
||||||
def cancelled(self, *, frames: int = 0) -> None:
|
def cancelled(self, *, frames: int = 0) -> None:
|
||||||
"""The pass was stopped: the result is incomplete and will not be played."""
|
"""The pass was stopped: the result is incomplete and will not be played."""
|
||||||
|
self._cancel_collapse()
|
||||||
self._state = CANCELLED
|
self._state = CANCELLED
|
||||||
self._cancelling = False
|
self._cancelling = False
|
||||||
self.progress_bar.setValue(0)
|
self.progress_bar.setValue(0)
|
||||||
@@ -212,6 +223,7 @@ class AnalysisProgress(QFrame):
|
|||||||
if cancelled:
|
if cancelled:
|
||||||
self.cancelled(frames=frames)
|
self.cancelled(frames=frames)
|
||||||
return
|
return
|
||||||
|
self._cancel_collapse()
|
||||||
self._state = FAILED
|
self._state = FAILED
|
||||||
self._cancelling = False
|
self._cancelling = False
|
||||||
self.progress_bar.setValue(0)
|
self.progress_bar.setValue(0)
|
||||||
@@ -222,6 +234,7 @@ class AnalysisProgress(QFrame):
|
|||||||
|
|
||||||
def clear(self) -> None:
|
def clear(self) -> None:
|
||||||
"""Hide the surface: no pass has anything to say."""
|
"""Hide the surface: no pass has anything to say."""
|
||||||
|
self._cancel_collapse()
|
||||||
self._state = IDLE
|
self._state = IDLE
|
||||||
self._cancelling = False
|
self._cancelling = False
|
||||||
self._detail = ""
|
self._detail = ""
|
||||||
@@ -247,6 +260,19 @@ class AnalysisProgress(QFrame):
|
|||||||
self.retranslate()
|
self.retranslate()
|
||||||
self.cancel_requested.emit()
|
self.cancel_requested.emit()
|
||||||
|
|
||||||
|
def _schedule_collapse(self) -> None:
|
||||||
|
"""Hide the banner after a short notice; keep ``state()`` for the rail."""
|
||||||
|
self._collapse_timer.start(_COLLAPSE_MS)
|
||||||
|
|
||||||
|
def _cancel_collapse(self) -> None:
|
||||||
|
if self._collapse_timer.isActive():
|
||||||
|
self._collapse_timer.stop()
|
||||||
|
|
||||||
|
def _collapse_success_banner(self) -> None:
|
||||||
|
"""Drop only the chrome; reused / complete remain the result's state."""
|
||||||
|
if self._state in (REUSED, COMPLETE):
|
||||||
|
self.setVisible(False)
|
||||||
|
|
||||||
def _refresh(self) -> None:
|
def _refresh(self) -> None:
|
||||||
"""Repaint from the state: colour, title, which chrome is offered, tooltip.
|
"""Repaint from the state: colour, title, which chrome is offered, tooltip.
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,14 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from orisys_app_shared.i18n import set_locale, t
|
from orisys_app_shared.i18n import set_locale, t
|
||||||
|
from PySide6.QtCore import QTimer
|
||||||
|
|
||||||
from orisys_studio.ui.analysis_progress import AnalysisProgress
|
from orisys_studio.ui.analysis_progress import (
|
||||||
|
COMPLETE,
|
||||||
|
REUSED,
|
||||||
|
AnalysisProgress,
|
||||||
|
_COLLAPSE_MS,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_analysis_progress_titles_name_the_outcome(qt_app) -> None:
|
def test_analysis_progress_titles_name_the_outcome(qt_app) -> None:
|
||||||
@@ -19,12 +25,57 @@ def test_analysis_progress_titles_name_the_outcome(qt_app) -> None:
|
|||||||
assert banner.title_label.text() == t("analysis.title_reused")
|
assert banner.title_label.text() == t("analysis.title_reused")
|
||||||
assert not banner.progress_bar.isVisible()
|
assert not banner.progress_bar.isVisible()
|
||||||
assert not banner.cancel_button.isVisible()
|
assert not banner.cancel_button.isVisible()
|
||||||
|
assert banner.isVisible()
|
||||||
|
assert banner.state() == REUSED
|
||||||
|
|
||||||
banner.complete(frames=12, elapsed_s=1.5)
|
banner.complete(frames=12, elapsed_s=1.5)
|
||||||
assert banner.title_label.text() == t("analysis.title_complete")
|
assert banner.title_label.text() == t("analysis.title_complete")
|
||||||
|
assert banner.isVisible()
|
||||||
|
assert banner.state() == COMPLETE
|
||||||
|
|
||||||
banner.cancelled(frames=3)
|
banner.cancelled(frames=3)
|
||||||
assert banner.title_label.text() == t("analysis.title_cancelled")
|
assert banner.title_label.text() == t("analysis.title_cancelled")
|
||||||
|
assert banner.isVisible()
|
||||||
|
|
||||||
banner.failed(reason="boom", frames=1)
|
banner.failed(reason="boom", frames=1)
|
||||||
assert banner.title_label.text() == t("analysis.title_failed")
|
assert banner.title_label.text() == t("analysis.title_failed")
|
||||||
|
assert banner.isVisible()
|
||||||
|
|
||||||
|
|
||||||
|
def test_reused_and_complete_banners_collapse_without_clearing_state(qt_app) -> None:
|
||||||
|
"""Success notices leave the rail's state intact after they stop taking height."""
|
||||||
|
set_locale("en_US")
|
||||||
|
banner = AnalysisProgress(theme="dark")
|
||||||
|
|
||||||
|
banner.reused(frames=12)
|
||||||
|
assert banner.isVisible()
|
||||||
|
QTimer.singleShot(0, lambda: None)
|
||||||
|
qt_app.processEvents()
|
||||||
|
# Advance past the collapse delay without waiting on the wall clock.
|
||||||
|
banner._collapse_timer.setInterval(0)
|
||||||
|
banner._collapse_timer.start()
|
||||||
|
qt_app.processEvents()
|
||||||
|
assert not banner.isVisible()
|
||||||
|
assert banner.state() == REUSED
|
||||||
|
|
||||||
|
banner.complete(frames=8, elapsed_s=0.5)
|
||||||
|
assert banner.isVisible()
|
||||||
|
banner._collapse_timer.setInterval(0)
|
||||||
|
banner._collapse_timer.start()
|
||||||
|
qt_app.processEvents()
|
||||||
|
assert not banner.isVisible()
|
||||||
|
assert banner.state() == COMPLETE
|
||||||
|
|
||||||
|
banner.cancelled(frames=1)
|
||||||
|
assert banner.isVisible()
|
||||||
|
assert banner.state() == "cancelled"
|
||||||
|
|
||||||
|
banner.failed(reason="disk", frames=0)
|
||||||
|
assert banner.isVisible()
|
||||||
|
assert banner.state() == "failed"
|
||||||
|
|
||||||
|
banner.start()
|
||||||
|
assert banner.isVisible()
|
||||||
|
assert banner.state() == "running"
|
||||||
|
assert not banner._collapse_timer.isActive()
|
||||||
|
assert _COLLAPSE_MS == 1500
|
||||||
|
|||||||
Reference in New Issue
Block a user