From c01a22147cb5f1016d27222ad0940658ab3e6d35 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 16 Mar 2021 22:38:55 +0100 Subject: [PATCH 1/5] moved model roles to constants --- pype/tools/launcher/constants.py | 12 ++++++++++++ pype/tools/launcher/models.py | 20 +++++++++++--------- pype/tools/launcher/widgets.py | 19 ++++++++++++++----- 3 files changed, 37 insertions(+), 14 deletions(-) create mode 100644 pype/tools/launcher/constants.py diff --git a/pype/tools/launcher/constants.py b/pype/tools/launcher/constants.py new file mode 100644 index 00000000000..e6dbbb6e192 --- /dev/null +++ b/pype/tools/launcher/constants.py @@ -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 diff --git a/pype/tools/launcher/models.py b/pype/tools/launcher/models.py index 631f6ddc988..b07321e4d6a 100644 --- a/pype/tools/launcher/models.py +++ b/pype/tools/launcher/models.py @@ -3,6 +3,12 @@ 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 +115,6 @@ def headerData(self, section, orientation, role): 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 @@ -235,8 +237,8 @@ def filter_actions(self): 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 +246,7 @@ def filter_actions(self): 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,8 +265,8 @@ def filter_actions(self): 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) diff --git a/pype/tools/launcher/widgets.py b/pype/tools/launcher/widgets.py index 42b24de8cd2..06c2daef9c6 100644 --- a/pype/tools/launcher/widgets.py +++ b/pype/tools/launcher/widgets.py @@ -7,6 +7,15 @@ 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 +114,7 @@ def __init__(self, dbcon, parent=None): # TODO better group delegate delegate = ActionDelegate( - [model.GROUP_ROLE, model.VARIANT_GROUP_ROLE], + [GROUP_ROLE, VARIANT_GROUP_ROLE], self ) view.setItemDelegate(delegate) @@ -136,14 +145,14 @@ def on_clicked(self, index): if 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.action_clicked.emit(action) return - actions = index.data(self.model.ACTION_ROLE) + actions = index.data(ACTION_ROLE) menu = QtWidgets.QMenu(self) actions_mapping = {} From ec7304414f589367a186822308e6c0386c40f697 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 16 Mar 2021 22:39:17 +0100 Subject: [PATCH 2/5] model store items by id --- pype/tools/launcher/models.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pype/tools/launcher/models.py b/pype/tools/launcher/models.py index b07321e4d6a..d1742014ef8 100644 --- a/pype/tools/launcher/models.py +++ b/pype/tools/launcher/models.py @@ -1,3 +1,4 @@ +import uuid import copy import logging import collections @@ -125,6 +126,7 @@ def __init__(self, dbcon, parent=None): 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.""" @@ -136,6 +138,7 @@ def discover(self): actions.extend(app_actions) self._registered_actions = actions + self.items_by_id.clear() def get_application_actions(self): actions = [] @@ -182,6 +185,7 @@ def filter_actions(self): # Validate actions based on compatibility self.clear() + self.items_by_id.clear() self._groups.clear() actions = self.filter_compatible_actions(self._registered_actions) @@ -272,6 +276,9 @@ def filter_actions(self): 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() From 745507f8d692c5ddcb8ff5e70be3ed3eff30f043 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 16 Mar 2021 22:40:37 +0100 Subject: [PATCH 3/5] implemented animation itself --- pype/tools/launcher/delegates.py | 58 ++++++++++++++++++++++++++++++++ pype/tools/launcher/widgets.py | 39 ++++++++++++++++++++- 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/pype/tools/launcher/delegates.py b/pype/tools/launcher/delegates.py index e2eecc6ad5f..9b88903bc14 100644 --- a/pype/tools/launcher/delegates.py +++ b/pype/tools/launcher/delegates.py @@ -1,4 +1,10 @@ +import time from Qt import QtCore, QtWidgets, QtGui +from .constants import ( + ANIMATION_LEN, + ANIMATION_START_ROLE, + ANIMATION_STATE_ROLE +) class ActionDelegate(QtWidgets.QStyledItemDelegate): @@ -9,8 +15,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 = ANIMATION_LEN / 4 + 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: diff --git a/pype/tools/launcher/widgets.py b/pype/tools/launcher/widgets.py index 06c2daef9c6..62545fb9660 100644 --- a/pype/tools/launcher/widgets.py +++ b/pype/tools/launcher/widgets.py @@ -1,4 +1,5 @@ import copy +import time import collections from Qt import QtWidgets, QtCore, QtGui from avalon.vendor import qtawesome @@ -124,6 +125,13 @@ def __init__(self, dbcon, parent=None): 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) @@ -141,8 +149,35 @@ def filter_actions(self): 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(GROUP_ROLE) @@ -150,6 +185,7 @@ def on_clicked(self, index): if not is_group and not is_variant_group: action = index.data(ACTION_ROLE) self.action_clicked.emit(action) + self._start_animation(index) return actions = index.data(ACTION_ROLE) @@ -213,6 +249,7 @@ def on_clicked(self, index): if result: action = actions_mapping[result] self.action_clicked.emit(action) + self._start_animation(index) class TasksWidget(QtWidgets.QWidget): From 66b82c39cd0afe50dd7cfb86f8476006630bf3eb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 17 Mar 2021 09:44:14 +0100 Subject: [PATCH 4/5] changed order of execution --- pype/tools/launcher/widgets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/tools/launcher/widgets.py b/pype/tools/launcher/widgets.py index 62545fb9660..9a7d8ca772c 100644 --- a/pype/tools/launcher/widgets.py +++ b/pype/tools/launcher/widgets.py @@ -184,8 +184,8 @@ def on_clicked(self, index): is_variant_group = index.data(VARIANT_GROUP_ROLE) if not is_group and not is_variant_group: action = index.data(ACTION_ROLE) - self.action_clicked.emit(action) self._start_animation(index) + self.action_clicked.emit(action) return actions = index.data(ACTION_ROLE) @@ -248,8 +248,8 @@ def on_clicked(self, index): result = menu.exec_(QtGui.QCursor.pos()) if result: action = actions_mapping[result] - self.action_clicked.emit(action) self._start_animation(index) + self.action_clicked.emit(action) class TasksWidget(QtWidgets.QWidget): From 34c8a113da4de302d084ccbfd5f58240692c9f6a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 17 Mar 2021 10:20:50 +0100 Subject: [PATCH 5/5] length of single orbit is not defined by animation length --- pype/tools/launcher/delegates.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pype/tools/launcher/delegates.py b/pype/tools/launcher/delegates.py index 9b88903bc14..cef0f5e1a2b 100644 --- a/pype/tools/launcher/delegates.py +++ b/pype/tools/launcher/delegates.py @@ -1,7 +1,6 @@ import time from Qt import QtCore, QtWidgets, QtGui from .constants import ( - ANIMATION_LEN, ANIMATION_START_ROLE, ANIMATION_STATE_ROLE ) @@ -47,7 +46,7 @@ def _draw_animation(self, painter, option, index): time_diff = time.time() - index.data(ANIMATION_START_ROLE) # Repeat 4 times - part_anim = ANIMATION_LEN / 4 + part_anim = 2.5 part_time = time_diff % part_anim offset = (part_time / part_anim) * 360 angle = (offset + 90) % 360