Merge pull request #3139 from pypeclub/enhancement/OP-3149_Publisher-UI-modifications

Publisher: UI Modifications and fixes
This commit is contained in:
Jakub Trllo 2022-05-10 14:35:02 +02:00 committed by GitHub
commit 95f2061714
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 668 additions and 137 deletions

View file

@ -2,10 +2,7 @@ from openpype.pipeline import (
Creator,
CreatedInstance
)
from openpype.lib import (
FileDef,
BoolDef,
)
from openpype.lib import FileDef
from .pipeline import (
list_instances,
@ -43,7 +40,6 @@ class TrayPublishCreator(Creator):
class SettingsCreator(TrayPublishCreator):
create_allow_context_change = True
enable_review = False
extensions = []
def collect_instances(self):
@ -67,19 +63,15 @@ class SettingsCreator(TrayPublishCreator):
self._add_instance_to_context(new_instance)
def get_instance_attr_defs(self):
output = []
file_def = FileDef(
"filepath",
folders=False,
extensions=self.extensions,
allow_sequences=self.allow_sequences,
label="Filepath",
)
output.append(file_def)
if self.enable_review:
output.append(BoolDef("review", label="Review"))
return output
return [
FileDef(
"filepath",
folders=False,
extensions=self.extensions,
allow_sequences=self.allow_sequences,
label="Filepath",
)
]
@classmethod
def from_settings(cls, item_data):
@ -97,7 +89,6 @@ class SettingsCreator(TrayPublishCreator):
"icon": item_data["icon"],
"description": item_data["description"],
"detailed_description": item_data["detailed_description"],
"enable_review": item_data["enable_review"],
"extensions": item_data["extensions"],
"allow_sequences": item_data["allow_sequences"],
"default_variants": item_data["default_variants"]

View file

@ -0,0 +1,31 @@
import pyblish.api
from openpype.lib import BoolDef
from openpype.pipeline import OpenPypePyblishPluginMixin
class CollectReviewFamily(
pyblish.api.InstancePlugin, OpenPypePyblishPluginMixin
):
"""Add review family."""
label = "Collect Review Family"
order = pyblish.api.CollectorOrder - 0.49
hosts = ["traypublisher"]
families = [
"image",
"render",
"plate",
"review"
]
def process(self, instance):
values = self.get_attr_values_from_data(instance.data)
if values.get("add_review_family"):
instance.data["families"].append("review")
@classmethod
def get_attribute_defs(cls):
return [
BoolDef("add_review_family", label="Review", default=True)
]

View file

@ -22,10 +22,6 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin):
repres = instance.data["representations"]
creator_attributes = instance.data["creator_attributes"]
if creator_attributes.get("review"):
instance.data["families"].append("review")
filepath_item = creator_attributes["filepath"]
self.log.info(filepath_item)
filepaths = [
@ -34,9 +30,11 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin):
]
instance.data["sourceFilepaths"] = filepaths
instance.data["stagingDir"] = filepath_item["directory"]
filenames = filepath_item["filenames"]
ext = os.path.splitext(filenames[0])[-1]
_, ext = os.path.splitext(filenames[0])
ext = ext[1:]
if len(filenames) == 1:
filenames = filenames[0]
@ -46,3 +44,7 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin):
"stagingDir": filepath_item["directory"],
"files": filenames
})
self.log.debug("Created Simple Settings instance {}".format(
instance.data
))

View file

@ -316,6 +316,7 @@ class FileDefItem(object):
self.is_sequence = False
self.template = None
self.frames = []
self.is_empty = True
self.set_filenames(filenames, frames, template)
@ -323,7 +324,9 @@ class FileDefItem(object):
return json.dumps(self.to_dict())
def __repr__(self):
if self.is_sequence:
if self.is_empty:
filename = "< empty >"
elif self.is_sequence:
filename = self.template
else:
filename = self.filenames[0]
@ -335,6 +338,9 @@ class FileDefItem(object):
@property
def label(self):
if self.is_empty:
return None
if not self.is_sequence:
return self.filenames[0]
@ -386,6 +392,8 @@ class FileDefItem(object):
@property
def ext(self):
if self.is_empty:
return None
_, ext = os.path.splitext(self.filenames[0])
if ext:
return ext
@ -393,6 +401,9 @@ class FileDefItem(object):
@property
def is_dir(self):
if self.is_empty:
return False
# QUESTION a better way how to define folder (in init argument?)
if self.ext:
return False
@ -411,6 +422,7 @@ class FileDefItem(object):
if is_sequence and not template:
raise ValueError("Missing template for sequence")
self.is_empty = len(filenames) == 0
self.filenames = filenames
self.template = template
self.frames = frames
@ -560,11 +572,7 @@ class FileDef(AbtractAttrDef):
# Change horizontal label
is_label_horizontal = kwargs.get("is_label_horizontal")
if is_label_horizontal is None:
if single_item:
is_label_horizontal = True
else:
is_label_horizontal = False
kwargs["is_label_horizontal"] = is_label_horizontal
kwargs["is_label_horizontal"] = False
self.single_item = single_item
self.folders = folders

View file

@ -8,7 +8,6 @@
"default_variants": [
"Main"
],
"enable_review": false,
"description": "Publish workfile backup",
"detailed_description": "",
"allow_sequences": true,

View file

@ -45,12 +45,6 @@
"type": "text"
}
},
{
"type": "boolean",
"key": "enable_review",
"label": "Enable review",
"tooltip": "Allow to create review from source file/s.\nFiles must be supported to be able create review."
},
{
"type": "separator"
},

View file

@ -856,18 +856,31 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
}
/* New Create/Publish UI */
#CreatorDetailedDescription {
padding-left: 5px;
padding-right: 5px;
padding-top: 5px;
background: transparent;
border: 1px solid {color:border};
}
#CreateDialogHelpButton {
background: rgba(255, 255, 255, 31);
border-top-left-radius: 0.2em;
border-bottom-left-radius: 0.2em;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
font-size: 10pt;
font-weight: bold;
padding: 3px 3px 3px 3px;
padding: 0px;
}
#CreateDialogHelpButton:hover {
background: rgba(255, 255, 255, 63);
}
#CreateDialogHelpButton QWidget {
background: transparent;
}
#PublishLogConsole {
font-family: "Noto Sans Mono";
@ -1014,7 +1027,44 @@ VariantInputsWidget QToolButton {
border-left: 1px solid {color:border};
}
#TasksCombobox[state="invalid"], #AssetNameInput[state="invalid"] {
#AssetNameInputWidget {
background: {color:bg-inputs};
border: 1px solid {color:border};
border-radius: 0.3em;
}
#AssetNameInputWidget QWidget {
background: transparent;
}
#AssetNameInputButton {
border-bottom-left-radius: 0px;
border-top-left-radius: 0px;
padding: 0px;
qproperty-iconSize: 11px 11px;
border-left: 1px solid {color:border};
border-right: none;
border-top: none;
border-bottom: none;
}
#AssetNameInput {
border-bottom-right-radius: 0px;
border-top-right-radius: 0px;
border: none;
}
#AssetNameInputWidget:hover {
border-color: {color:border-hover};
}
#AssetNameInputWidget:focus{
border-color: {color:border-focus};
}
#AssetNameInputWidget:disabled {
background: {color:bg-inputs-disabled};
}
#TasksCombobox[state="invalid"], #AssetNameInputWidget[state="invalid"], #AssetNameInputButton[state="invalid"] {
border-color: {color:publisher:error};
}

View file

@ -15,6 +15,7 @@ from openpype.tools.utils.assets_widget import (
class CreateDialogAssetsWidget(SingleSelectAssetsWidget):
current_context_required = QtCore.Signal()
header_height_changed = QtCore.Signal(int)
def __init__(self, controller, parent):
self._controller = controller
@ -27,6 +28,27 @@ class CreateDialogAssetsWidget(SingleSelectAssetsWidget):
self._last_selection = None
self._enabled = None
self._last_filter_height = None
def _check_header_height(self):
"""Catch header height changes.
Label on top of creaters should have same height so Creators view has
same offset.
"""
height = self.header_widget.height()
if height != self._last_filter_height:
self._last_filter_height = height
self.header_height_changed.emit(height)
def resizeEvent(self, event):
super(CreateDialogAssetsWidget, self).resizeEvent(event)
self._check_header_height()
def showEvent(self, event):
super(CreateDialogAssetsWidget, self).showEvent(event)
self._check_header_height()
def _on_current_asset_click(self):
self.current_context_required.emit()
@ -71,6 +93,7 @@ class AssetsHierarchyModel(QtGui.QStandardItemModel):
Uses controller to load asset hierarchy. All asset documents are stored by
their parents.
"""
def __init__(self, controller):
super(AssetsHierarchyModel, self).__init__()
self._controller = controller
@ -143,6 +166,7 @@ class AssetsHierarchyModel(QtGui.QStandardItemModel):
class AssetsDialog(QtWidgets.QDialog):
"""Dialog to select asset for a context of instance."""
def __init__(self, controller, parent):
super(AssetsDialog, self).__init__(parent)
self.setWindowTitle("Select asset")
@ -196,9 +220,26 @@ class AssetsDialog(QtWidgets.QDialog):
# - adds ability to call reset on multiple places without repeating
self._soft_reset_enabled = True
self._first_show = True
self._default_height = 500
def _on_first_show(self):
center = self.rect().center()
size = self.size()
size.setHeight(self._default_height)
self.resize(size)
new_pos = self.mapToGlobal(center)
new_pos.setX(new_pos.x() - int(self.width() / 2))
new_pos.setY(new_pos.y() - int(self.height() / 2))
self.move(new_pos)
def showEvent(self, event):
"""Refresh asset model on show."""
super(AssetsDialog, self).showEvent(event)
if self._first_show:
self._first_show = False
self._on_first_show()
# Refresh on show
self.reset(False)

View file

@ -3,6 +3,7 @@ import re
import traceback
import copy
import qtawesome
try:
import commonmark
except Exception:
@ -15,7 +16,8 @@ from openpype.pipeline.create import (
)
from openpype.tools.utils import (
ErrorMessageBox,
MessageOverlayObject
MessageOverlayObject,
ClickableFrame,
)
from .widgets import IconValuePixmapLabel
@ -114,6 +116,8 @@ class CreateErrorMessageBox(ErrorMessageBox):
# TODO add creator identifier/label to details
class CreatorShortDescWidget(QtWidgets.QWidget):
height_changed = QtCore.Signal(int)
def __init__(self, parent=None):
super(CreatorShortDescWidget, self).__init__(parent=parent)
@ -152,6 +156,22 @@ class CreatorShortDescWidget(QtWidgets.QWidget):
self._family_label = family_label
self._description_label = description_label
self._last_height = None
def _check_height_change(self):
height = self.height()
if height != self._last_height:
self._last_height = height
self.height_changed.emit(height)
def showEvent(self, event):
super(CreatorShortDescWidget, self).showEvent(event)
self._check_height_change()
def resizeEvent(self, event):
super(CreatorShortDescWidget, self).resizeEvent(event)
self._check_height_change()
def set_plugin(self, plugin=None):
if not plugin:
self._icon_widget.set_icon_def(None)
@ -168,13 +188,43 @@ class CreatorShortDescWidget(QtWidgets.QWidget):
self._description_label.setText(description)
class HelpButton(QtWidgets.QPushButton):
resized = QtCore.Signal()
class HelpButton(ClickableFrame):
resized = QtCore.Signal(int)
question_mark_icon_name = "fa.question"
help_icon_name = "fa.question-circle"
hide_icon_name = "fa.angle-left"
def __init__(self, *args, **kwargs):
super(HelpButton, self).__init__(*args, **kwargs)
self.setObjectName("CreateDialogHelpButton")
question_mark_label = QtWidgets.QLabel(self)
help_widget = QtWidgets.QWidget(self)
help_question = QtWidgets.QLabel(help_widget)
help_label = QtWidgets.QLabel("Help", help_widget)
hide_icon = QtWidgets.QLabel(help_widget)
help_layout = QtWidgets.QHBoxLayout(help_widget)
help_layout.setContentsMargins(0, 0, 5, 0)
help_layout.addWidget(help_question, 0)
help_layout.addWidget(help_label, 0)
help_layout.addStretch(1)
help_layout.addWidget(hide_icon, 0)
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
layout.addWidget(question_mark_label, 0)
layout.addWidget(help_widget, 1)
help_widget.setVisible(False)
self._question_mark_label = question_mark_label
self._help_widget = help_widget
self._help_question = help_question
self._hide_icon = hide_icon
self._expanded = None
self.set_expanded()
@ -184,31 +234,56 @@ class HelpButton(QtWidgets.QPushButton):
return
expanded = False
self._expanded = expanded
if expanded:
text = "<"
self._help_widget.setVisible(expanded)
self._update_content()
def _update_content(self):
width = self.get_icon_width()
if self._expanded:
question_mark_pix = QtGui.QPixmap(width, width)
question_mark_pix.fill(QtCore.Qt.transparent)
else:
text = "?"
self.setText(text)
question_mark_icon = qtawesome.icon(
self.question_mark_icon_name, color=QtCore.Qt.white
)
question_mark_pix = question_mark_icon.pixmap(width, width)
self._update_size()
hide_icon = qtawesome.icon(
self.hide_icon_name, color=QtCore.Qt.white
)
help_question_icon = qtawesome.icon(
self.help_icon_name, color=QtCore.Qt.white
)
self._question_mark_label.setPixmap(question_mark_pix)
self._question_mark_label.setMaximumWidth(width)
self._hide_icon.setPixmap(hide_icon.pixmap(width, width))
self._help_question.setPixmap(help_question_icon.pixmap(width, width))
def _update_size(self):
new_size = self.minimumSizeHint()
if self.size() != new_size:
self.resize(new_size)
self.resized.emit()
def get_icon_width(self):
metrics = self.fontMetrics()
return metrics.height()
def set_pos_and_size(self, pos_x, pos_y, width, height):
update_icon = self.height() != height
self.move(pos_x, pos_y)
self.resize(width, height)
if update_icon:
self._update_content()
self.updateGeometry()
def showEvent(self, event):
super(HelpButton, self).showEvent(event)
self._update_size()
self.resized.emit(self.height())
def resizeEvent(self, event):
super(HelpButton, self).resizeEvent(event)
self._update_size()
self.resized.emit(self.height())
class CreateDialog(QtWidgets.QDialog):
default_size = (900, 500)
default_size = (1000, 560)
def __init__(
self, controller, asset_name=None, task_name=None, parent=None
@ -255,6 +330,14 @@ class CreateDialog(QtWidgets.QDialog):
context_layout.addWidget(tasks_widget, 1)
# --- Creators view ---
creators_header_widget = QtWidgets.QWidget(self)
header_label_widget = QtWidgets.QLabel(
"Choose family:", creators_header_widget
)
creators_header_layout = QtWidgets.QHBoxLayout(creators_header_widget)
creators_header_layout.setContentsMargins(0, 0, 0, 0)
creators_header_layout.addWidget(header_label_widget, 1)
creators_view = QtWidgets.QListView(self)
creators_model = QtGui.QStandardItemModel()
creators_view.setModel(creators_model)
@ -271,7 +354,6 @@ class CreateDialog(QtWidgets.QDialog):
variant_hints_menu = QtWidgets.QMenu(variant_widget)
variant_hints_group = QtWidgets.QActionGroup(variant_hints_menu)
# variant_hints_btn.setMenu(variant_hints_menu)
variant_layout = QtWidgets.QHBoxLayout(variant_widget)
variant_layout.setContentsMargins(0, 0, 0, 0)
@ -282,9 +364,6 @@ class CreateDialog(QtWidgets.QDialog):
subset_name_input = QtWidgets.QLineEdit(self)
subset_name_input.setEnabled(False)
create_btn = QtWidgets.QPushButton("Create", self)
create_btn.setEnabled(False)
form_layout = QtWidgets.QFormLayout()
form_layout.addRow("Variant:", variant_widget)
form_layout.addRow("Subset:", subset_name_input)
@ -292,10 +371,9 @@ class CreateDialog(QtWidgets.QDialog):
mid_widget = QtWidgets.QWidget(self)
mid_layout = QtWidgets.QVBoxLayout(mid_widget)
mid_layout.setContentsMargins(0, 0, 0, 0)
mid_layout.addWidget(QtWidgets.QLabel("Choose family:", self))
mid_layout.addWidget(creators_header_widget, 0)
mid_layout.addWidget(creators_view, 1)
mid_layout.addLayout(form_layout, 0)
mid_layout.addWidget(create_btn, 0)
# ------------
# --- Creator short info and attr defs ---
@ -305,31 +383,62 @@ class CreateDialog(QtWidgets.QDialog):
creator_attrs_widget
)
separator_widget = QtWidgets.QWidget(self)
separator_widget.setObjectName("Separator")
separator_widget.setMinimumHeight(2)
separator_widget.setMaximumHeight(2)
attr_separator_widget = QtWidgets.QWidget(self)
attr_separator_widget.setObjectName("Separator")
attr_separator_widget.setMinimumHeight(1)
attr_separator_widget.setMaximumHeight(1)
# Precreate attributes widget
pre_create_widget = PreCreateWidget(creator_attrs_widget)
# Create button
create_btn_wrapper = QtWidgets.QWidget(creator_attrs_widget)
create_btn = QtWidgets.QPushButton("Create", create_btn_wrapper)
create_btn.setEnabled(False)
create_btn_wrap_layout = QtWidgets.QHBoxLayout(create_btn_wrapper)
create_btn_wrap_layout.setContentsMargins(0, 0, 0, 0)
create_btn_wrap_layout.addStretch(1)
create_btn_wrap_layout.addWidget(create_btn, 0)
creator_attrs_layout = QtWidgets.QVBoxLayout(creator_attrs_widget)
creator_attrs_layout.setContentsMargins(0, 0, 0, 0)
creator_attrs_layout.addWidget(creator_short_desc_widget, 0)
creator_attrs_layout.addWidget(separator_widget, 0)
creator_attrs_layout.addWidget(attr_separator_widget, 0)
creator_attrs_layout.addWidget(pre_create_widget, 1)
creator_attrs_layout.addWidget(create_btn_wrapper, 0)
# -------------------------------------
# --- Detailed information about creator ---
# Detailed description of creator
detail_description_widget = QtWidgets.QTextEdit(self)
detail_description_widget.setObjectName("InfoText")
detail_description_widget.setTextInteractionFlags(
detail_description_widget = QtWidgets.QWidget(self)
detail_placoholder_widget = QtWidgets.QWidget(
detail_description_widget
)
detail_placoholder_widget.setAttribute(
QtCore.Qt.WA_TranslucentBackground
)
detail_description_input = QtWidgets.QTextEdit(
detail_description_widget
)
detail_description_input.setObjectName("CreatorDetailedDescription")
detail_description_input.setTextInteractionFlags(
QtCore.Qt.TextBrowserInteraction
)
detail_description_widget.setVisible(False)
# -------------------------------------------
detail_description_layout = QtWidgets.QVBoxLayout(
detail_description_widget
)
detail_description_layout.setContentsMargins(0, 0, 0, 0)
detail_description_layout.setSpacing(0)
detail_description_layout.addWidget(detail_placoholder_widget, 0)
detail_description_layout.addWidget(detail_description_input, 1)
detail_description_widget.setVisible(False)
# -------------------------------------------
splitter_widget = QtWidgets.QSplitter(self)
splitter_widget.addWidget(context_widget)
splitter_widget.addWidget(mid_widget)
@ -344,17 +453,27 @@ class CreateDialog(QtWidgets.QDialog):
layout.addWidget(splitter_widget, 1)
# Floating help button
# - Create this button as last to be fully visible
help_btn = HelpButton(self)
prereq_timer = QtCore.QTimer()
prereq_timer.setInterval(50)
prereq_timer.setSingleShot(True)
desc_width_anim_timer = QtCore.QTimer()
desc_width_anim_timer.setInterval(10)
prereq_timer.timeout.connect(self._on_prereq_timer)
desc_width_anim_timer.timeout.connect(self._on_desc_animation)
help_btn.clicked.connect(self._on_help_btn)
help_btn.resized.connect(self._on_help_btn_resize)
assets_widget.header_height_changed.connect(
self._on_asset_filter_height_change
)
create_btn.clicked.connect(self._on_create)
variant_widget.resized.connect(self._on_variant_widget_resize)
variant_input.returnPressed.connect(self._on_create)
@ -369,6 +488,10 @@ class CreateDialog(QtWidgets.QDialog):
self._on_current_session_context_request
)
tasks_widget.task_changed.connect(self._on_task_change)
creator_short_desc_widget.height_changed.connect(
self._on_description_height_change
)
splitter_widget.splitterMoved.connect(self._on_splitter_move)
controller.add_plugins_refresh_callback(self._on_plugins_refresh)
@ -387,18 +510,33 @@ class CreateDialog(QtWidgets.QDialog):
self.variant_hints_menu = variant_hints_menu
self.variant_hints_group = variant_hints_group
self._creators_header_widget = creators_header_widget
self.creators_model = creators_model
self.creators_view = creators_view
self.create_btn = create_btn
self._creator_short_desc_widget = creator_short_desc_widget
self._pre_create_widget = pre_create_widget
self._attr_separator_widget = attr_separator_widget
self._detail_placoholder_widget = detail_placoholder_widget
self._detail_description_widget = detail_description_widget
self._detail_description_input = detail_description_input
self._help_btn = help_btn
self._prereq_timer = prereq_timer
self._first_show = True
# Description animation
self._description_size_policy = detail_description_widget.sizePolicy()
self._desc_width_anim_timer = desc_width_anim_timer
self._desc_widget_step = 0
self._last_description_width = None
self._last_full_width = 0
self._expected_description_width = 0
self._last_desc_max_width = None
self._other_widgets_widths = []
def _emit_message(self, message):
self._overlay_object.add_message(message)
@ -465,6 +603,10 @@ class CreateDialog(QtWidgets.QDialog):
def _invalidate_prereq(self):
self._prereq_timer.start()
def _on_asset_filter_height_change(self, height):
self._creators_header_widget.setMinimumHeight(height)
self._creators_header_widget.setMaximumHeight(height)
def _on_prereq_timer(self):
prereq_available = True
creator_btn_tooltips = []
@ -595,6 +737,12 @@ class CreateDialog(QtWidgets.QDialog):
if self._task_name:
self._tasks_widget.select_task_name(self._task_name)
def _on_description_height_change(self):
# Use separator's 'y' position as height
height = self._attr_separator_widget.y()
self._detail_placoholder_widget.setMinimumHeight(height)
self._detail_placoholder_widget.setMaximumHeight(height)
def _on_creator_item_change(self, new_index, _old_index):
identifier = None
if new_index.isValid():
@ -602,54 +750,192 @@ class CreateDialog(QtWidgets.QDialog):
self._set_creator_by_identifier(identifier)
def _update_help_btn(self):
pos_x = self.width() - self._help_btn.width()
point = self._creator_short_desc_widget.rect().topRight()
mapped_point = self._creator_short_desc_widget.mapTo(self, point)
pos_y = mapped_point.y()
self._help_btn.move(max(0, pos_x), max(0, pos_y))
short_desc_rect = self._creator_short_desc_widget.rect()
def _on_help_btn_resize(self):
# point = short_desc_rect.topRight()
point = short_desc_rect.center()
mapped_point = self._creator_short_desc_widget.mapTo(self, point)
# pos_y = mapped_point.y()
center_pos_y = mapped_point.y()
icon_width = self._help_btn.get_icon_width()
_height = int(icon_width * 2.5)
height = min(_height, short_desc_rect.height())
pos_y = center_pos_y - int(height / 2)
pos_x = self.width() - icon_width
if self._detail_placoholder_widget.isVisible():
pos_x -= (
self._detail_placoholder_widget.width()
+ self._splitter_widget.handle(3).width()
)
width = self.width() - pos_x
self._help_btn.set_pos_and_size(
max(0, pos_x), max(0, pos_y),
width, height
)
def _on_help_btn_resize(self, height):
if self._creator_short_desc_widget.height() != height:
self._update_help_btn()
def _on_splitter_move(self, *args):
self._update_help_btn()
def _on_help_btn(self):
if self._desc_width_anim_timer.isActive():
return
final_size = self.size()
cur_sizes = self._splitter_widget.sizes()
spacing = self._splitter_widget.handleWidth()
if self._desc_widget_step == 0:
now_visible = self._detail_description_widget.isVisible()
else:
now_visible = self._desc_widget_step > 0
sizes = []
for idx, value in enumerate(cur_sizes):
if idx < 3:
sizes.append(value)
now_visible = self._detail_description_widget.isVisible()
self._last_full_width = final_size.width()
self._other_widgets_widths = list(sizes)
if now_visible:
width = final_size.width() - (
spacing + self._detail_description_widget.width()
)
cur_desc_width = self._detail_description_widget.width()
if cur_desc_width < 1:
cur_desc_width = 2
step_size = int(cur_desc_width / 5)
if step_size < 1:
step_size = 1
step_size *= -1
expected_width = 0
desc_width = cur_desc_width - 1
width = final_size.width() - 1
min_max = desc_width
self._last_description_width = cur_desc_width
else:
last_size = self._detail_description_widget.sizeHint().width()
width = final_size.width() + spacing + last_size
sizes.append(last_size)
self._detail_description_widget.setVisible(True)
handle = self._splitter_widget.handle(3)
desc_width = handle.sizeHint().width()
if self._last_description_width:
expected_width = self._last_description_width
else:
hint = self._detail_description_widget.sizeHint()
expected_width = hint.width()
width = final_size.width() + desc_width
step_size = int(expected_width / 5)
if step_size < 1:
step_size = 1
min_max = 0
if self._last_desc_max_width is None:
self._last_desc_max_width = (
self._detail_description_widget.maximumWidth()
)
self._detail_description_widget.setMinimumWidth(min_max)
self._detail_description_widget.setMaximumWidth(min_max)
self._expected_description_width = expected_width
self._desc_widget_step = step_size
self._desc_width_anim_timer.start()
sizes.append(desc_width)
final_size.setWidth(width)
self._detail_description_widget.setVisible(not now_visible)
self._splitter_widget.setSizes(sizes)
self.resize(final_size)
self._help_btn.set_expanded(not now_visible)
def _on_desc_animation(self):
current_width = self._detail_description_widget.width()
desc_width = None
last_step = False
growing = self._desc_widget_step > 0
# Growing
if growing:
if current_width < self._expected_description_width:
desc_width = current_width + self._desc_widget_step
if desc_width >= self._expected_description_width:
desc_width = self._expected_description_width
last_step = True
# Decreasing
elif self._desc_widget_step < 0:
if current_width > self._expected_description_width:
desc_width = current_width + self._desc_widget_step
if desc_width <= self._expected_description_width:
desc_width = self._expected_description_width
last_step = True
if desc_width is None:
self._desc_widget_step = 0
self._desc_width_anim_timer.stop()
return
if last_step and not growing:
self._detail_description_widget.setVisible(False)
QtWidgets.QApplication.processEvents()
width = self._last_full_width
handle_width = self._splitter_widget.handle(3).width()
if growing:
width += (handle_width + desc_width)
else:
width -= self._last_description_width
if last_step:
width -= handle_width
else:
width += desc_width
if not last_step or growing:
self._detail_description_widget.setMaximumWidth(desc_width)
self._detail_description_widget.setMinimumWidth(desc_width)
window_size = self.size()
window_size.setWidth(width)
self.resize(window_size)
if not last_step:
return
self._desc_widget_step = 0
self._desc_width_anim_timer.stop()
if not growing:
return
self._detail_description_widget.setMinimumWidth(0)
self._detail_description_widget.setMaximumWidth(
self._last_desc_max_width
)
self._detail_description_widget.setSizePolicy(
self._description_size_policy
)
sizes = list(self._other_widgets_widths)
sizes.append(desc_width)
self._splitter_widget.setSizes(sizes)
def _set_creator_detailed_text(self, creator):
if not creator:
self._detail_description_widget.setPlainText("")
self._detail_description_input.setPlainText("")
return
detailed_description = creator.get_detail_description() or ""
if commonmark:
html = commonmark.commonmark(detailed_description)
self._detail_description_widget.setHtml(html)
self._detail_description_input.setHtml(html)
else:
self._detail_description_widget.setMarkdown(detailed_description)
self._detail_description_input.setMarkdown(detailed_description)
def _set_creator_by_identifier(self, identifier):
creator = self.controller.manual_creators.get(identifier)
@ -806,6 +1092,21 @@ class CreateDialog(QtWidgets.QDialog):
self.variant_input.setProperty("state", state)
self.variant_input.style().polish(self.variant_input)
def _on_first_show(self):
center = self.rect().center()
width, height = self.default_size
self.resize(width, height)
part = int(width / 7)
self._splitter_widget.setSizes(
[part * 2, part * 2, width - (part * 4)]
)
new_pos = self.mapToGlobal(center)
new_pos.setX(new_pos.x() - int(self.width() / 2))
new_pos.setY(new_pos.y() - int(self.height() / 2))
self.move(new_pos)
def moveEvent(self, event):
super(CreateDialog, self).moveEvent(event)
self._last_pos = self.pos()
@ -814,13 +1115,7 @@ class CreateDialog(QtWidgets.QDialog):
super(CreateDialog, self).showEvent(event)
if self._first_show:
self._first_show = False
width, height = self.default_size
self.resize(width, height)
third_size = int(width / 3)
self._splitter_widget.setSizes(
[third_size, third_size, width - (2 * third_size)]
)
self._on_first_show()
if self._last_pos is not None:
self.move(self._last_pos)

View file

@ -14,7 +14,8 @@ from openpype.tools.utils import (
PlaceholderLineEdit,
IconButton,
PixmapLabel,
BaseClickableFrame
BaseClickableFrame,
set_style_property,
)
from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS
from .assets_widget import AssetsDialog
@ -344,21 +345,42 @@ class AssetsField(BaseClickableFrame):
def __init__(self, controller, parent):
super(AssetsField, self).__init__(parent)
self.setObjectName("AssetNameInputWidget")
dialog = AssetsDialog(controller, self)
# Don't use 'self' for parent!
# - this widget has specific styles
dialog = AssetsDialog(controller, parent)
name_input = ClickableLineEdit(self)
name_input.setObjectName("AssetNameInput")
icon_name = "fa.window-maximize"
icon = qtawesome.icon(icon_name, color="white")
icon_btn = QtWidgets.QPushButton(self)
icon_btn.setIcon(icon)
icon_btn.setObjectName("AssetNameInputButton")
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
layout.addWidget(name_input, 1)
layout.addWidget(icon_btn, 0)
# Make sure all widgets are vertically extended to highest widget
for widget in (
name_input,
icon_btn
):
size_policy = widget.sizePolicy()
size_policy.setVerticalPolicy(size_policy.MinimumExpanding)
widget.setSizePolicy(size_policy)
name_input.clicked.connect(self._mouse_release_callback)
icon_btn.clicked.connect(self._mouse_release_callback)
dialog.finished.connect(self._on_dialog_finish)
self._dialog = dialog
self._name_input = name_input
self._icon_btn = icon_btn
self._origin_value = []
self._origin_selection = []
@ -406,10 +428,9 @@ class AssetsField(BaseClickableFrame):
self._set_state_property(state)
def _set_state_property(self, state):
current_value = self._name_input.property("state")
if current_value != state:
self._name_input.setProperty("state", state)
self._name_input.style().polish(self._name_input)
set_style_property(self, "state", state)
set_style_property(self._name_input, "state", state)
set_style_property(self._icon_btn, "state", state)
def is_valid(self):
"""Is asset valid."""
@ -842,6 +863,8 @@ class VariantInputWidget(PlaceholderLineEdit):
self._ignore_value_change = True
self._has_value_changed = False
self._origin_value = list(variants)
self._current_value = list(variants)
@ -892,11 +915,23 @@ class MultipleItemWidget(QtWidgets.QWidget):
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(view)
model.rowsInserted.connect(self._on_insert)
self._view = view
self._model = model
self._value = []
def _on_insert(self):
self._update_size()
def _update_size(self):
model = self._view.model()
if model.rowCount() == 0:
return
height = self._view.sizeHintForRow(0)
self.setMaximumHeight(height + (2 * self._view.spacing()))
def showEvent(self, event):
super(MultipleItemWidget, self).showEvent(event)
tmp_item = None
@ -904,13 +939,15 @@ class MultipleItemWidget(QtWidgets.QWidget):
# Add temp item to be able calculate maximum height of widget
tmp_item = QtGui.QStandardItem("tmp")
self._model.appendRow(tmp_item)
height = self._view.sizeHintForRow(0)
self.setMaximumHeight(height + (2 * self._view.spacing()))
self._update_size()
if tmp_item is not None:
self._model.clear()
def resizeEvent(self, event):
super(MultipleItemWidget, self).resizeEvent(event)
self._update_size()
def set_value(self, value=None):
"""Set value/s of currently selected instance."""
if value is None:
@ -1235,7 +1272,11 @@ class CreatorAttrsWidget(QtWidgets.QWidget):
)
content_widget = QtWidgets.QWidget(self._scroll_area)
content_layout = QtWidgets.QFormLayout(content_widget)
content_layout = QtWidgets.QGridLayout(content_widget)
content_layout.setColumnStretch(0, 0)
content_layout.setColumnStretch(1, 1)
row = 0
for attr_def, attr_instances, values in result:
widget = create_widget_for_attr_def(attr_def, content_widget)
if attr_def.is_value_def:
@ -1246,10 +1287,28 @@ class CreatorAttrsWidget(QtWidgets.QWidget):
else:
widget.set_value(values, True)
label = attr_def.label or attr_def.key
content_layout.addRow(label, widget)
widget.value_changed.connect(self._input_value_changed)
expand_cols = 2
if attr_def.is_value_def and attr_def.is_label_horizontal:
expand_cols = 1
col_num = 2 - expand_cols
label = attr_def.label or attr_def.key
if label:
label_widget = QtWidgets.QLabel(label, self)
content_layout.addWidget(
label_widget, row, 0, 1, expand_cols
)
if not attr_def.is_label_horizontal:
row += 1
content_layout.addWidget(
widget, row, col_num, 1, expand_cols
)
row += 1
widget.value_changed.connect(self._input_value_changed)
self._attr_def_id_to_instances[attr_def.id] = attr_instances
self._attr_def_id_to_attr_def[attr_def.id] = attr_def

View file

@ -589,10 +589,12 @@ class AssetsWidget(QtWidgets.QWidget):
view = AssetsView(self)
view.setModel(proxy)
header_widget = QtWidgets.QWidget(self)
current_asset_icon = qtawesome.icon(
"fa.arrow-down", color=get_default_tools_icon_color()
)
current_asset_btn = QtWidgets.QPushButton(self)
current_asset_btn = QtWidgets.QPushButton(header_widget)
current_asset_btn.setIcon(current_asset_icon)
current_asset_btn.setToolTip("Go to Asset from current Session")
# Hide by default
@ -601,25 +603,35 @@ class AssetsWidget(QtWidgets.QWidget):
refresh_icon = qtawesome.icon(
"fa.refresh", color=get_default_tools_icon_color()
)
refresh_btn = QtWidgets.QPushButton(self)
refresh_btn = QtWidgets.QPushButton(header_widget)
refresh_btn.setIcon(refresh_icon)
refresh_btn.setToolTip("Refresh items")
filter_input = PlaceholderLineEdit(self)
filter_input = PlaceholderLineEdit(header_widget)
filter_input.setPlaceholderText("Filter assets..")
# Header
header_layout = QtWidgets.QHBoxLayout()
header_layout = QtWidgets.QHBoxLayout(header_widget)
header_layout.setContentsMargins(0, 0, 0, 0)
header_layout.addWidget(filter_input)
header_layout.addWidget(current_asset_btn)
header_layout.addWidget(refresh_btn)
# Make header widgets expand vertically if there is a place
for widget in (
current_asset_btn,
refresh_btn,
filter_input,
):
size_policy = widget.sizePolicy()
size_policy.setVerticalPolicy(size_policy.MinimumExpanding)
widget.setSizePolicy(size_policy)
# Layout
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(4)
layout.addLayout(header_layout)
layout.addWidget(view)
layout.addWidget(header_widget, 0)
layout.addWidget(view, 1)
# Signals/Slots
filter_input.textChanged.connect(self._on_filter_text_change)
@ -630,6 +642,8 @@ class AssetsWidget(QtWidgets.QWidget):
current_asset_btn.clicked.connect(self._on_current_asset_click)
view.doubleClicked.connect(self.double_clicked)
self._header_widget = header_widget
self._filter_input = filter_input
self._refresh_btn = refresh_btn
self._current_asset_btn = current_asset_btn
self._model = model
@ -637,8 +651,14 @@ class AssetsWidget(QtWidgets.QWidget):
self._view = view
self._last_project_name = None
self._last_btns_height = None
self.model_selection = {}
@property
def header_widget(self):
return self._header_widget
def _create_source_model(self):
model = AssetModel(dbcon=self.dbcon, parent=self)
model.refreshed.connect(self._on_model_refresh)
@ -669,6 +689,7 @@ class AssetsWidget(QtWidgets.QWidget):
This separation gives ability to override this method and use it
in differnt way.
"""
self.set_current_session_asset()
def set_current_session_asset(self):
@ -681,6 +702,7 @@ class AssetsWidget(QtWidgets.QWidget):
Some tools may have their global refresh button or do not support
refresh at all.
"""
if visible is None:
visible = not self._refresh_btn.isVisible()
self._refresh_btn.setVisible(visible)
@ -690,6 +712,7 @@ class AssetsWidget(QtWidgets.QWidget):
Not all tools support using of current context asset.
"""
if visible is None:
visible = not self._current_asset_btn.isVisible()
self._current_asset_btn.setVisible(visible)
@ -723,6 +746,7 @@ class AssetsWidget(QtWidgets.QWidget):
so if you're modifying model keep in mind that this method should be
called when refresh is done.
"""
self._proxy.sort(0)
self._set_loading_state(loading=False, empty=not has_item)
self.refreshed.emit()
@ -767,6 +791,7 @@ class SingleSelectAssetsWidget(AssetsWidget):
Contain single selection specific api methods.
"""
def get_selected_asset_id(self):
"""Currently selected asset id."""
selection_model = self._view.selectionModel()

View file

@ -151,7 +151,7 @@ class FilesModel(QtGui.QStandardItemModel):
item = QtGui.QStandardItem()
item_id = str(uuid.uuid4())
item.setData(item_id, ITEM_ID_ROLE)
item.setData(file_item.label, ITEM_LABEL_ROLE)
item.setData(file_item.label or "< empty >", ITEM_LABEL_ROLE)
item.setData(file_item.filenames, FILENAMES_ROLE)
item.setData(file_item.directory, DIRPATH_ROLE)
item.setData(icon_pixmap, ITEM_ICON_ROLE)
@ -251,7 +251,7 @@ class FilesProxyModel(QtCore.QSortFilterProxyModel):
class ItemWidget(QtWidgets.QWidget):
split_requested = QtCore.Signal(str)
context_menu_requested = QtCore.Signal(QtCore.QPoint)
def __init__(
self, item_id, label, pixmap_icon, is_sequence, multivalue, parent=None
@ -316,19 +316,9 @@ class ItemWidget(QtWidgets.QWidget):
self._update_btn_size()
def _on_actions_clicked(self):
menu = QtWidgets.QMenu(self._split_btn)
action = QtWidgets.QAction("Split sequence", menu)
action.triggered.connect(self._on_split_sequence)
menu.addAction(action)
pos = self._split_btn.rect().bottomLeft()
point = self._split_btn.mapToGlobal(pos)
menu.popup(point)
def _on_split_sequence(self):
self.split_requested.emit(self._item_id)
self.context_menu_requested.emit(point)
class InViewButton(IconButton):
@ -339,6 +329,7 @@ class FilesView(QtWidgets.QListView):
"""View showing instances and their groups."""
remove_requested = QtCore.Signal()
context_menu_requested = QtCore.Signal(QtCore.QPoint)
def __init__(self, *args, **kwargs):
super(FilesView, self).__init__(*args, **kwargs)
@ -347,6 +338,7 @@ class FilesView(QtWidgets.QListView):
self.setSelectionMode(
QtWidgets.QAbstractItemView.ExtendedSelection
)
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
remove_btn = InViewButton(self)
pix_enabled = paint_image_with_color(
@ -361,6 +353,7 @@ class FilesView(QtWidgets.QListView):
remove_btn.setEnabled(False)
remove_btn.clicked.connect(self._on_remove_clicked)
self.customContextMenuRequested.connect(self._on_context_menu_request)
self._remove_btn = remove_btn
@ -397,6 +390,12 @@ class FilesView(QtWidgets.QListView):
selected_item_ids.add(instance_id)
return selected_item_ids
def has_selected_sequence(self):
for index in self.selectionModel().selectedIndexes():
if index.data(IS_SEQUENCE_ROLE):
return True
return False
def event(self, event):
if event.type() == QtCore.QEvent.KeyPress:
if (
@ -408,6 +407,12 @@ class FilesView(QtWidgets.QListView):
return super(FilesView, self).event(event)
def _on_context_menu_request(self, pos):
index = self.indexAt(pos)
if index.isValid():
point = self.viewport().mapToGlobal(pos)
self.context_menu_requested.emit(point)
def _on_selection_change(self):
self._remove_btn.setEnabled(self.has_selected_item_ids())
@ -456,6 +461,9 @@ class FilesWidget(QtWidgets.QFrame):
files_proxy_model.rowsInserted.connect(self._on_rows_inserted)
files_proxy_model.rowsRemoved.connect(self._on_rows_removed)
files_view.remove_requested.connect(self._on_remove_requested)
files_view.context_menu_requested.connect(
self._on_context_menu_requested
)
self._in_set_value = False
self._single_item = single_item
self._multivalue = False
@ -504,7 +512,9 @@ class FilesWidget(QtWidgets.QFrame):
return file_items
if file_items:
return file_items[0]
return FileDefItem.create_empty_item()
empty_item = FileDefItem.create_empty_item()
return empty_item.to_dict()
def set_filters(self, folders_allowed, exts_filter):
self._files_proxy_model.set_allow_folders(folders_allowed)
@ -527,7 +537,9 @@ class FilesWidget(QtWidgets.QFrame):
is_sequence,
self._multivalue
)
widget.split_requested.connect(self._on_split_request)
widget.context_menu_requested.connect(
self._on_context_menu_requested
)
self._files_view.setIndexWidget(index, widget)
self._files_proxy_model.setData(
index, widget.sizeHint(), QtCore.Qt.SizeHintRole
@ -559,17 +571,22 @@ class FilesWidget(QtWidgets.QFrame):
if not self._in_set_value:
self.value_changed.emit()
def _on_split_request(self, item_id):
def _on_split_request(self):
if self._multivalue:
return
file_item = self._files_model.get_file_item_by_id(item_id)
if not file_item:
item_ids = self._files_view.get_selected_item_ids()
if not item_ids:
return
new_items = file_item.split_sequence()
self._remove_item_by_ids([item_id])
self._add_filepaths(new_items)
for item_id in item_ids:
file_item = self._files_model.get_file_item_by_id(item_id)
if not file_item:
return
new_items = file_item.split_sequence()
self._add_filepaths(new_items)
self._remove_item_by_ids(item_ids)
def _on_remove_requested(self):
if self._multivalue:
@ -579,6 +596,23 @@ class FilesWidget(QtWidgets.QFrame):
if items_to_delete:
self._remove_item_by_ids(items_to_delete)
def _on_context_menu_requested(self, pos):
if self._multivalue:
return
menu = QtWidgets.QMenu(self._files_view)
if self._files_view.has_selected_sequence():
split_action = QtWidgets.QAction("Split sequence", menu)
split_action.triggered.connect(self._on_split_request)
menu.addAction(split_action)
remove_action = QtWidgets.QAction("Remove", menu)
remove_action.triggered.connect(self._on_remove_requested)
menu.addAction(remove_action)
menu.popup(pos)
def sizeHint(self):
# Get size hints of widget and visible widgets
result = super(FilesWidget, self).sizeHint()

View file

@ -91,6 +91,8 @@ class AttributeDefinitionsWidget(QtWidgets.QWidget):
layout.deleteLater()
new_layout = QtWidgets.QGridLayout()
new_layout.setColumnStretch(0, 0)
new_layout.setColumnStretch(1, 1)
self.setLayout(new_layout)
def set_attr_defs(self, attr_defs):