Skip to content

Commit

Permalink
feat: add loading animation
Browse files Browse the repository at this point in the history
  • Loading branch information
vzhd1701 committed Jun 16, 2022
1 parent 6c4494e commit d874bea
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 23 deletions.
6 changes: 4 additions & 2 deletions gridplayer/widgets/video_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
from gridplayer.widgets.video_frame_vlc_base import VideoFrameVLC
from gridplayer.widgets.video_overlay import OverlayBlock, OverlayBlockFloating

IN_PROGRESS_THRESHOLD_MS = 500


class QStackedLayoutFloating(QStackedLayout):
"""overridden setGeometry for children is not honored due to type casting inside Qt,
Expand Down Expand Up @@ -157,7 +159,7 @@ def __init__(self, video_driver, **kwargs):

self._in_progress_timer = QTimer(self)
self._in_progress_timer.setSingleShot(True)
self._in_progress_timer.setInterval(500)
self._in_progress_timer.setInterval(IN_PROGRESS_THRESHOLD_MS)
self._in_progress_timer.timeout.connect(self.is_in_progress_change)

self.url_resolver = self.init_url_resolver()
Expand Down Expand Up @@ -316,7 +318,7 @@ def set_status(self, status):
self.overlay.hide()
self.video_driver.hide()

self.status_label.set_icon(status)
self.status_label.icon = status
self.status_label.show()
self.repaint()

Expand Down
215 changes: 199 additions & 16 deletions gridplayer/widgets/video_block_status.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,227 @@
from PyQt5.QtCore import QSize, Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QGraphicsOpacityEffect, QLabel
from PyQt5.QtCore import QRectF, QSize, Qt
from PyQt5.QtGui import QColor, QIcon, QPainter
from PyQt5.QtWidgets import (
QGraphicsOpacityEffect,
QHBoxLayout,
QLabel,
QSizePolicy,
QWidget,
qApp,
)

from gridplayer.utils.darkmode import is_dark_mode

class StatusLabel(QLabel):

def bezier_blend(t):
return t * t * (3.0 - 2.0 * t) # noqa: WPS432


class JumpingBar(QWidget):
bar_grow_speed = 1.5
bar_wane_speed = 1

bar_lowest_percent = 30
animation_fps = 100

def __init__(self, start_percent=100, is_growing=False, **kwargs):
super().__init__(**kwargs)

self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.animation_fps = min(
self.animation_fps, int(qApp.primaryScreen().refreshRate())
)

self._color = QColor(Qt.white) if is_dark_mode() else QColor(Qt.black)
self._timer = None

self.bar_percent = min(max(start_percent, self.bar_lowest_percent), 100)
self.is_growing = is_growing

@property
def bar_percent_smooth(self):
return bezier_blend(self.bar_percent / 100) * 100

@property
def roundness(self):
return min(self.width() / 3, 10)

def paintEvent(self, event):
painter = QPainter(self)

painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(self._color)
painter.setBrush(self._color)

middle = self.rect().center()

bar_height = (self.rect().height() - 10) * (self.bar_percent_smooth / 100)

bar_rect = QRectF(
0,
middle.y() - bar_height / 2,
self.rect().width(),
bar_height,
)

painter.drawRoundedRect(bar_rect, self.roundness, self.roundness)

def timerEvent(self, event):
if self.is_growing:
self.bar_percent += self.bar_grow_speed

if self.bar_percent >= 100:
self.bar_percent = 100
self.is_growing = False
else:
self.bar_percent -= self.bar_wane_speed

if self.bar_percent <= self.bar_lowest_percent:
self.bar_percent = self.bar_lowest_percent
self.is_growing = True

self.update()

def showEvent(self, event) -> None:
# start animation

if self._timer is None:
self._timer = self.startTimer(1000 // self.animation_fps)

def hideEvent(self, event) -> None:
# stop animation

if self._timer is not None:
self.killTimer(self._timer)
self._timer = None


class LoadingBars(QWidget):
def __init__(self, **kwargs):
super().__init__(**kwargs)

layout = QHBoxLayout(self)
layout.setAlignment(Qt.AlignCenter)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)

self.bars = [
JumpingBar(start_percent=start_percent, parent=self)
for start_percent in (100, 70, 50, 70, 100)
]

for cur_bar in self.bars:
layout.addWidget(cur_bar)
if cur_bar != self.bars[-1]:
layout.addStretch()

def resizeEvent(self, event):
# maintain 1:1 ratio

size_min = min(event.size().width(), event.size().height())

h_margin = round((self.size().width() - size_min) / 2)

self.layout().setContentsMargins(h_margin, 0, h_margin, 0)


class StatusLabel(QWidget):
def __init__(self, icon="processing", **kwargs):
super().__init__(**kwargs)

self.setup_ui()

self.status_bars = LoadingBars(parent=self)
self.status_icon = StatusIcon(parent=self)

self.layout().addWidget(self.status_bars)
self.layout().addWidget(self.status_icon)

self.icon = icon

def setup_ui(self):
self.setAutoFillBackground(True)

# Due to Qt video block glitch, cannot hide video block
# Due to VLC video block glitch, cannot hide video block
# workaround - make overlay loading screen 99% opaque
# so it will appear visually solid while video block is invisible
effect = QGraphicsOpacityEffect(self)
almost_opaque = 0.99
effect.setOpacity(almost_opaque)
self.setGraphicsEffect(effect)

layout = QHBoxLayout(self)
layout.setAlignment(Qt.AlignCenter)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)

def resizeEvent(self, event):
# maintain 10% margin

v_margin = int(event.size().height() * 0.1)
h_margin = int(event.size().width() * 0.1)

self.layout().setContentsMargins(h_margin, v_margin, h_margin, v_margin)

@property
def icon(self):
return self._icon

@icon.setter
def icon(self, icon):
self._icon = icon

if icon == "processing":
self.status_bars.show()

self.status_icon.hide()
else:
self.status_bars.hide()

self.status_icon.icon = icon
self.status_icon.show()


class StatusIcon(QLabel):
def __init__(self, icon=None, **kwargs):
super().__init__(**kwargs)

self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.setAlignment(Qt.AlignCenter)

self.set_icon(icon)
self._pic = None

self.icon = icon

def resizeEvent(self, event):
self._set_pic_to_half_size(event.size())
self._scale_pic(event.size())

@property
def icon(self):
return self._icon

@icon.setter
def icon(self, icon):
self._icon = icon

def set_icon(self, icon):
self._set_pic(QIcon.fromTheme(icon))
if icon is None:
self._pic = None
self.clear()
else:
self._set_pic(QIcon.fromTheme(icon))

def _set_pic(self, pic_path):
reasonably_big = 512
self.pic = QIcon(pic_path).pixmap(QSize(reasonably_big, reasonably_big))

self._set_pic_to_half_size(self.size())
self._pic = QIcon(pic_path).pixmap(QSize(reasonably_big, reasonably_big))

def _set_pic_to_half_size(self, size):
half_size_multiplier = 0.75
self._scale_pic(self.size())

width = int(size.width() * half_size_multiplier)
height = int(size.height() * half_size_multiplier)
def _scale_pic(self, size):
if self._pic is None:
return

self.setPixmap(
self.pic.scaled(width, height, Qt.KeepAspectRatio, Qt.SmoothTransformation)
self._pic.scaled(
size.width(), size.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation
)
)
6 changes: 1 addition & 5 deletions gridplayer/widgets/video_overlay_icons.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@ def draw_volume_on(rect, painter, color_fg, color_bg):
draw_volume_off(rect, painter, color_fg, color_bg)

painter.fillRect(
rect.x() + 5 + 6 + 3,
rect.y() + 6,
4,
rect.height() - 12,
color_fg, # noqa: WPS432
rect.x() + 5 + 6 + 3, rect.y() + 6, 4, rect.height() - 12, color_fg
)


Expand Down

0 comments on commit d874bea

Please sign in to comment.