Show incoming fight reactions on unfocused visible players
This commit is contained in:
@@ -36,7 +36,8 @@ stay in chat history; joining or reconnecting does not replay animations.
|
|||||||
|
|
||||||
Quiet sound effects play automatically with visible reactions. Sounds are
|
Quiet sound effects play automatically with visible reactions. Sounds are
|
||||||
original synthesized effects cached locally, with no external downloads. Overlays and sound are
|
original synthesized effects cached locally, with no external downloads. Overlays and sound are
|
||||||
suppressed while the player is hidden, minimized, or another application is active.
|
suppressed while the player is hidden or minimized. Visible players receive
|
||||||
|
reactions even without keyboard focus, without activating the player window.
|
||||||
Reactions also work while the detached chat window is focused.
|
Reactions also work while the detached chat window is focused.
|
||||||
|
|
||||||
Robbery votes allow one answer per person, which you can change before the vote
|
Robbery votes allow one answer per person, which you can change before the vote
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import wave
|
|||||||
|
|
||||||
from PyQt6.QtCore import Qt, QTimer, QPropertyAnimation, QVariantAnimation, QPoint, QEasingCurve, QEvent
|
from PyQt6.QtCore import Qt, QTimer, QPropertyAnimation, QVariantAnimation, QPoint, QEasingCurve, QEvent
|
||||||
from PyQt6.QtGui import QPainter, QPainterPath, QFont, QColor, QPen, QLinearGradient, QTransform
|
from PyQt6.QtGui import QPainter, QPainterPath, QFont, QColor, QPen, QLinearGradient, QTransform
|
||||||
from PyQt6.QtWidgets import QApplication, QWidget, QFrame, QLabel, QHBoxLayout, QVBoxLayout
|
from PyQt6.QtWidgets import QWidget, QFrame, QLabel, QHBoxLayout, QVBoxLayout
|
||||||
|
|
||||||
|
|
||||||
REACTIONS = {
|
REACTIONS = {
|
||||||
@@ -157,11 +157,7 @@ class RingsideStrip(QWidget):
|
|||||||
if kind not in REACTIONS or not self.video_frame.isVisible():
|
if kind not in REACTIONS or not self.video_frame.isVisible():
|
||||||
return False
|
return False
|
||||||
owner = self.video_frame.window()
|
owner = self.video_frame.window()
|
||||||
# Avoid displaying an always-on-top effect over another application.
|
if owner.isMinimized():
|
||||||
active = QApplication.activeWindow()
|
|
||||||
while active is not None and active is not owner:
|
|
||||||
active = active.parentWidget()
|
|
||||||
if active is None or owner.isMinimized():
|
|
||||||
return False
|
return False
|
||||||
if self.owner_window is not owner:
|
if self.owner_window is not owner:
|
||||||
if self.owner_window is not None:
|
if self.owner_window is not None:
|
||||||
@@ -211,7 +207,9 @@ class RingsideStrip(QWidget):
|
|||||||
|
|
||||||
def eventFilter(self, watched, event):
|
def eventFilter(self, watched, event):
|
||||||
if self.isVisible():
|
if self.isVisible():
|
||||||
if event.type() in (QEvent.Type.Hide, QEvent.Type.WindowDeactivate, QEvent.Type.Close):
|
if event.type() in (QEvent.Type.Hide, QEvent.Type.Close):
|
||||||
|
self.dismiss()
|
||||||
|
elif event.type() == QEvent.Type.WindowStateChange and self.owner_window.isMinimized():
|
||||||
self.dismiss()
|
self.dismiss()
|
||||||
elif event.type() in (QEvent.Type.Move, QEvent.Type.Resize):
|
elif event.type() in (QEvent.Type.Move, QEvent.Type.Resize):
|
||||||
self.slide.stop()
|
self.slide.stop()
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ def test_strip_is_native_positioned_and_does_not_steal_focus(room, qtbot, kind):
|
|||||||
qtbot.wait(50)
|
qtbot.wait(50)
|
||||||
bounds = QRect(video.mapToGlobal(QPoint(0, 0)), video.size())
|
bounds = QRect(video.mapToGlobal(QPoint(0, 0)), video.size())
|
||||||
assert bounds.contains(strip.geometry())
|
assert bounds.contains(strip.geometry())
|
||||||
QApplication.sendEvent(room, QEvent(QEvent.Type.WindowDeactivate))
|
QApplication.sendEvent(room, QEvent(QEvent.Type.Hide))
|
||||||
assert not strip.isVisible()
|
assert not strip.isVisible()
|
||||||
|
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ def test_arcade_ko_covers_video_then_restores_strip(room, qtbot):
|
|||||||
assert overlay.ko_animation.state() == overlay.ko_animation.State.Stopped
|
assert overlay.ko_animation.state() == overlay.ko_animation.State.Stopped
|
||||||
|
|
||||||
|
|
||||||
def test_sound_is_enabled_by_default_and_background_effects_are_suppressed(room, qtbot, monkeypatch):
|
def test_sound_is_enabled_by_default_and_hidden_effects_are_suppressed(room, qtbot, monkeypatch):
|
||||||
play = Mock()
|
play = Mock()
|
||||||
monkeypatch.setattr(room, '_play_reaction_sound', play)
|
monkeypatch.setattr(room, '_play_reaction_sound', play)
|
||||||
room.activateWindow()
|
room.activateWindow()
|
||||||
@@ -109,6 +109,29 @@ def test_sound_is_enabled_by_default_and_background_effects_are_suppressed(room,
|
|||||||
assert not room.ringside_strip.isVisible()
|
assert not room.ringside_strip.isVisible()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('kind', ['ko', 'bell'])
|
||||||
|
def test_receiving_client_shows_reaction_without_focus(room, qtbot, monkeypatch, kind):
|
||||||
|
from PyQt6.QtWidgets import QLineEdit
|
||||||
|
other_client = QLineEdit()
|
||||||
|
qtbot.addWidget(other_client)
|
||||||
|
other_client.show()
|
||||||
|
other_client.activateWindow()
|
||||||
|
other_client.setFocus()
|
||||||
|
qtbot.wait(30)
|
||||||
|
assert not room.isActiveWindow()
|
||||||
|
play = Mock()
|
||||||
|
monkeypatch.setattr(room, '_play_reaction_sound', play)
|
||||||
|
room.handle_fight_reaction({'kind': kind, 'username': 'Other client', 'position': 0})
|
||||||
|
assert room.ringside_strip.isVisible()
|
||||||
|
play.assert_called_once_with(kind)
|
||||||
|
assert QApplication.focusWidget() is other_client
|
||||||
|
QApplication.sendEvent(room, QEvent(QEvent.Type.WindowDeactivate))
|
||||||
|
assert room.ringside_strip.isVisible()
|
||||||
|
room.showMinimized()
|
||||||
|
qtbot.wait(30)
|
||||||
|
assert not room.ringside_strip.isVisible()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('kind', REACTIONS)
|
@pytest.mark.parametrize('kind', REACTIONS)
|
||||||
def test_original_sound_files_are_valid_and_loadable(qtbot, tmp_path, kind):
|
def test_original_sound_files_are_valid_and_loadable(qtbot, tmp_path, kind):
|
||||||
path = write_reaction_sound(tmp_path, kind)
|
path = write_reaction_sound(tmp_path, kind)
|
||||||
|
|||||||
Reference in New Issue
Block a user