Merge pull request #1143 from pypeclub/feature/launcher_animation

Animated launch in launcher tool
This commit is contained in:
Milan Kolar 2021-03-17 10:55:44 +01:00 committed by GitHub
commit 05f4b77d6b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 139 additions and 15 deletions

View file

@ -0,0 +1,12 @@
from Qt import QtCore
ACTION_ROLE = QtCore.Qt.UserRole
GROUP_ROLE = QtCore.Qt.UserRole + 1
VARIANT_GROUP_ROLE = QtCore.Qt.UserRole + 2
ACTION_ID_ROLE = QtCore.Qt.UserRole + 3
ANIMATION_START_ROLE = QtCore.Qt.UserRole + 4
ANIMATION_STATE_ROLE = QtCore.Qt.UserRole + 5
ANIMATION_LEN = 10

View file

@ -1,4 +1,9 @@
import time
from Qt import QtCore, QtWidgets, QtGui
from .constants import (
ANIMATION_START_ROLE,
ANIMATION_STATE_ROLE
)
class ActionDelegate(QtWidgets.QStyledItemDelegate):
@ -9,8 +14,60 @@ class ActionDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self, group_roles, *args, **kwargs):
super(ActionDelegate, self).__init__(*args, **kwargs)
self.group_roles = group_roles
self._anim_start_color = QtGui.QColor(178, 255, 246)
self._anim_end_color = QtGui.QColor(5, 44, 50)
def _draw_animation(self, painter, option, index):
grid_size = option.widget.gridSize()
x_offset = int(
(grid_size.width() / 2)
- (option.rect.width() / 2)
)
item_x = option.rect.x() - x_offset
rect_offset = grid_size.width() / 20
size = grid_size.width() - (rect_offset * 2)
anim_rect = QtCore.QRect(
item_x + rect_offset,
option.rect.y() + rect_offset,
size,
size
)
painter.save()
painter.setBrush(QtCore.Qt.transparent)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
gradient = QtGui.QConicalGradient()
gradient.setCenter(anim_rect.center())
gradient.setColorAt(0, self._anim_start_color)
gradient.setColorAt(1, self._anim_end_color)
time_diff = time.time() - index.data(ANIMATION_START_ROLE)
# Repeat 4 times
part_anim = 2.5
part_time = time_diff % part_anim
offset = (part_time / part_anim) * 360
angle = (offset + 90) % 360
gradient.setAngle(-angle)
pen = QtGui.QPen(QtGui.QBrush(gradient), rect_offset)
pen.setCapStyle(QtCore.Qt.RoundCap)
painter.setPen(pen)
painter.drawArc(
anim_rect,
-16 * (angle + 10),
-16 * offset
)
painter.restore()
def paint(self, painter, option, index):
if index.data(ANIMATION_STATE_ROLE):
self._draw_animation(painter, option, index)
super(ActionDelegate, self).paint(painter, option, index)
is_group = False
for group_role in self.group_roles:

View file

@ -1,8 +1,15 @@
import uuid
import copy
import logging
import collections
from . import lib
from .constants import (
ACTION_ROLE,
GROUP_ROLE,
VARIANT_GROUP_ROLE,
ACTION_ID_ROLE
)
from .actions import ApplicationAction
from Qt import QtCore, QtGui
from avalon.vendor import qtawesome
@ -109,10 +116,6 @@ class TaskModel(QtGui.QStandardItemModel):
class ActionModel(QtGui.QStandardItemModel):
ACTION_ROLE = QtCore.Qt.UserRole
GROUP_ROLE = QtCore.Qt.UserRole + 1
VARIANT_GROUP_ROLE = QtCore.Qt.UserRole + 2
def __init__(self, dbcon, parent=None):
super(ActionModel, self).__init__(parent=parent)
self.dbcon = dbcon
@ -123,6 +126,7 @@ class ActionModel(QtGui.QStandardItemModel):
self.default_icon = qtawesome.icon("fa.cube", color="white")
# Cache of available actions
self._registered_actions = list()
self.items_by_id = {}
def discover(self):
"""Set up Actions cache. Run this for each new project."""
@ -134,6 +138,7 @@ class ActionModel(QtGui.QStandardItemModel):
actions.extend(app_actions)
self._registered_actions = actions
self.items_by_id.clear()
def get_application_actions(self):
actions = []
@ -180,6 +185,7 @@ class ActionModel(QtGui.QStandardItemModel):
# Validate actions based on compatibility
self.clear()
self.items_by_id.clear()
self._groups.clear()
actions = self.filter_compatible_actions(self._registered_actions)
@ -235,8 +241,8 @@ class ActionModel(QtGui.QStandardItemModel):
item = QtGui.QStandardItem(icon, label)
item.setData(label, QtCore.Qt.ToolTipRole)
item.setData(actions, self.ACTION_ROLE)
item.setData(True, self.VARIANT_GROUP_ROLE)
item.setData(actions, ACTION_ROLE)
item.setData(True, VARIANT_GROUP_ROLE)
items_by_order[order].append(item)
for action in single_actions:
@ -244,7 +250,7 @@ class ActionModel(QtGui.QStandardItemModel):
label = lib.get_action_label(action)
item = QtGui.QStandardItem(icon, label)
item.setData(label, QtCore.Qt.ToolTipRole)
item.setData(action, self.ACTION_ROLE)
item.setData(action, ACTION_ROLE)
items_by_order[action.order].append(item)
for group_name, actions in grouped_actions.items():
@ -263,13 +269,16 @@ class ActionModel(QtGui.QStandardItemModel):
icon = self.default_icon
item = QtGui.QStandardItem(icon, group_name)
item.setData(actions, self.ACTION_ROLE)
item.setData(True, self.GROUP_ROLE)
item.setData(actions, ACTION_ROLE)
item.setData(True, GROUP_ROLE)
items_by_order[order].append(item)
for order in sorted(items_by_order.keys()):
for item in items_by_order[order]:
item_id = str(uuid.uuid4())
item.setData(item_id, ACTION_ID_ROLE)
self.items_by_id[item_id] = item
self.appendRow(item)
self.endResetModel()

View file

@ -1,4 +1,5 @@
import copy
import time
import collections
from Qt import QtWidgets, QtCore, QtGui
from avalon.vendor import qtawesome
@ -7,6 +8,15 @@ from .delegates import ActionDelegate
from . import lib
from .models import TaskModel, ActionModel, ProjectModel
from .flickcharm import FlickCharm
from .constants import (
ACTION_ROLE,
GROUP_ROLE,
VARIANT_GROUP_ROLE,
ACTION_ID_ROLE,
ANIMATION_START_ROLE,
ANIMATION_STATE_ROLE,
ANIMATION_LEN
)
class ProjectBar(QtWidgets.QWidget):
@ -105,7 +115,7 @@ class ActionBar(QtWidgets.QWidget):
# TODO better group delegate
delegate = ActionDelegate(
[model.GROUP_ROLE, model.VARIANT_GROUP_ROLE],
[GROUP_ROLE, VARIANT_GROUP_ROLE],
self
)
view.setItemDelegate(delegate)
@ -115,6 +125,13 @@ class ActionBar(QtWidgets.QWidget):
self.model = model
self.view = view
self._animated_items = set()
animation_timer = QtCore.QTimer()
animation_timer.setInterval(50)
animation_timer.timeout.connect(self._on_animation)
self._animation_timer = animation_timer
# Make view flickable
flick = FlickCharm(parent=view)
flick.activateOn(view)
@ -132,18 +149,46 @@ class ActionBar(QtWidgets.QWidget):
def set_row_height(self, rows):
self.setMinimumHeight(rows * 75)
def _on_animation(self):
time_now = time.time()
for action_id in tuple(self._animated_items):
item = self.model.items_by_id.get(action_id)
if not item:
self._animated_items.remove(action_id)
continue
start_time = item.data(ANIMATION_START_ROLE)
if (time_now - start_time) > ANIMATION_LEN:
item.setData(0, ANIMATION_STATE_ROLE)
self._animated_items.remove(action_id)
if not self._animated_items:
self._animation_timer.stop()
self.update()
def _start_animation(self, index):
action_id = index.data(ACTION_ID_ROLE)
item = self.model.items_by_id.get(action_id)
if item:
item.setData(time.time(), ANIMATION_START_ROLE)
item.setData(1, ANIMATION_STATE_ROLE)
self._animated_items.add(action_id)
self._animation_timer.start()
def on_clicked(self, index):
if not index.isValid():
if not index or not index.isValid():
return
is_group = index.data(self.model.GROUP_ROLE)
is_variant_group = index.data(self.model.VARIANT_GROUP_ROLE)
is_group = index.data(GROUP_ROLE)
is_variant_group = index.data(VARIANT_GROUP_ROLE)
if not is_group and not is_variant_group:
action = index.data(self.model.ACTION_ROLE)
action = index.data(ACTION_ROLE)
self._start_animation(index)
self.action_clicked.emit(action)
return
actions = index.data(self.model.ACTION_ROLE)
actions = index.data(ACTION_ROLE)
menu = QtWidgets.QMenu(self)
actions_mapping = {}
@ -203,6 +248,7 @@ class ActionBar(QtWidgets.QWidget):
result = menu.exec_(QtGui.QCursor.pos())
if result:
action = actions_mapping[result]
self._start_animation(index)
self.action_clicked.emit(action)