Создание горизонтальной Kanban доски с PySide2 - PullRequest
0 голосов
/ 20 января 2020

Я ищу информацию о том, как go создать горизонтальную доску в стиле Kanban, используя PySide2. Мое приложение - это файловый браузер, в котором вы выбираете элемент из папки QTreeView слева, а в правом окне отображаются карточки. С правой стороны карты я в тупике.

Вот моя цель:

enter image description here

Моя текущая реализация wip использует QTreeView для отображения карт - это близко, но не совсем то, что я ищу. В настоящее время делегат dr aws родители в качестве псевдо заголовков, а дети в виде карточек. Как вы можете видеть ниже, одной из проблем использования QTreeView является то, что дочерние элементы перечислены по вертикали, а не по моему предпочтительному горизонтальному списку.

Моя текущая реализация wip:

enter image description here

У меня есть несколько идей о том, как go об этом:

  1. Используйте setIndexWidget(), чтобы добавить QListView под каждым псевдо заголовком родительский элемент. Я не уверен, является ли это предполагаемым использованием этого метода или как получить данные модели для заполнения списка.
  2. Заменить или покрыть QListView на QWidget, который динамически заполняется данными модели из выбора папки просмотра. Этот виджет создает QLabels и QlistViews для каждого элемента заголовка из модели. Я чувствую, что все усложняется, и решение, модифицирующее существующее представление, вероятно, будет лучше в долгосрочной перспективе.
  3. Используйте другое представление, о котором я не знаю!

Любые мысли о том, как go о создании этого? Вертикальный Kanban виджет существует? Спасибо!

Кроме того, вот некоторые другие просмотры, которые я пробовал:

список

enter image description here

столбец просмотр

enter image description here

Пример кода:

import os
import sys
import collections

from PySide2 import QtWidgets, QtGui, QtCore


class MainWidget(QtWidgets.QWidget):

    def __init__(self, model_data):
        super(MainWidget, self).__init__()

        self.setMinimumSize(600, 500)

        # Model
        self.model_data = model_data
        self.folder_model = QtGui.QStandardItemModel()
        self._fill_model(model_data)

        # Folder view
        self.folders_view = QtWidgets.QTreeView()
        self.folders_view.setModel(self.folder_model)
        self.folders_view.expandAll()
        self.folders_view.setItemsExpandable(False)
        self.folders_view.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)

        # Files Delegate
        self.files_delegate = FilesItemDelegate()
        self.folders_view.setItemDelegate(self.files_delegate)

        # Layout
        self.main_layout = QtWidgets.QHBoxLayout()
        self.main_layout.addWidget(self.folders_view)
        self.main_layout.setContentsMargins(0, 0, 0, 0)

        self.setLayout(self.main_layout)

    def _fill_model(self, value, parent=None):
        if isinstance(value, collections.abc.Mapping):
            for key, val in sorted(value.items()):

                if key == 'meta_data':
                    pass
                else:
                    item = QtGui.QStandardItem(key)
                    item.setData(val['meta_data']['name'], QtCore.Qt.DisplayRole)
                    item.setData(val['meta_data']['item_type'], QtCore.Qt.UserRole + 1)

                    try:  # special data for major items
                        item.setData(val['meta_data']['major_number'], QtCore.Qt.UserRole)
                    except KeyError:
                        pass
                    try:  # special data for minor items
                        item.setData(val['meta_data']['comment'], QtCore.Qt.UserRole)
                    except KeyError:
                        pass

                    try:  # Add row under a parent item
                        parent.appendRow(item)
                        self._fill_model(value=val, parent=item)

                    except AttributeError:  # Add first item to model
                        self.folder_model.appendRow(item)
                        self._fill_model(value=val, parent=item)


class FilesItemDelegate(QtWidgets.QStyledItemDelegate):
    def __init__(self, parent=None):
        super(FilesItemDelegate, self).__init__(parent)

    def sizeHint(self, option, index):

        if index.data(role=QtCore.Qt.UserRole + 1) == 'major':

            size = super(FilesItemDelegate, self).sizeHint(option, index)

            size.setWidth(option.rect.width())
            size.setHeight(50)
            return size

        elif index.data(role=QtCore.Qt.UserRole + 1) == 'minor':

            size = super(FilesItemDelegate, self).sizeHint(option, index)

            if option.state & QtWidgets.QStyle.State_Selected:
                width = 250
            elif option.state & QtWidgets.QStyle.State_MouseOver:
                width = 250
            else:
                width = 200

            size.setWidth(width)
            size.setHeight(200)
            return size

        else:
            return super(FilesItemDelegate, self).sizeHint(option, index)

    def paint(self, painter, option, index):

        # Rect
        rect_item = option.rect

        # Background
        painter.setPen(QtCore.Qt.NoPen)

        # File component
        if index.data(role=QtCore.Qt.UserRole + 1) == 'major':

            # Rects:
            rect_header_icon = QtCore.QRect(
                rect_item.left() + 7,
                rect_item.top(),
                50,
                rect_item.height() - 3)
            rect_header_name = QtCore.QRect(
                rect_header_icon.right() + 8,
                rect_item.top(),
                rect_item.width() - rect_header_icon.width(),
                rect_item.height() * .666)
            rect_header_major = QtCore.QRect(
                rect_header_icon.right() + 8,
                rect_header_name.bottom(),
                rect_item.width() - rect_header_icon.width(),
                rect_item.height() * .333)

            if option.state & QtWidgets.QStyle.State_Selected:
                painter.setBrush(QtGui.QColor('#00000000'))
            elif option.state & QtWidgets.QStyle.State_MouseOver:
                painter.setBrush(QtGui.QColor('#00000000'))
            else:
                painter.setBrush(QtGui.QColor('#00000000'))
            painter.drawRect(option.rect)

            # Icon
            painter.save()
            painter.setBrush(QtGui.QColor('#262626'))
            painter.drawRect(rect_header_icon)
            painter.restore()

            # Primary Title
            painter.setPen(QtGui.QColor(100, 100, 100))

            # Name
            name_index = index.data(role=QtCore.Qt.DisplayRole)
            name_font = QtGui.QFont("Segoe UI", 13, QtGui.QFont.DemiBold | QtGui.QFont.NoAntialias)
            painter.setFont(name_font)
            QtWidgets.QApplication.style().drawItemText(painter, rect_header_name, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter,
                                                        QtWidgets.QApplication.palette(), True,
                                                        name_index)
            # Major number
            name_index = index.data(role=QtCore.Qt.UserRole)
            name_font = QtGui.QFont("Segoe UI", 13, QtGui.QFont.DemiBold | QtGui.QFont.NoAntialias)
            painter.setFont(name_font)
            QtWidgets.QApplication.style().drawItemText(painter, rect_header_major,
                                                        QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter,
                                                        QtWidgets.QApplication.palette(), True,
                                                        name_index)

        # Cards
        elif index.data(role=QtCore.Qt.UserRole + 1) == 'minor':

            # Rects
            rect_icon_target = QtCore.QRect(
                rect_item.left(),
                rect_item.top(),
                rect_item.width(),
                rect_item.height() - int(rect_item.height()/2)
            )
            rect_data_target = QtCore.QRect(
                rect_item.left(),
                rect_icon_target.bottom(),
                rect_item.width(),
                rect_item.height() - int(rect_item.height()/2)
            )

            pad_data_target = 15
            rect_name_target = QtCore.QRect(
                rect_item.left() + pad_data_target,
                rect_icon_target.bottom() + pad_data_target,
                rect_item.width() - pad_data_target,
                rect_data_target.height()/2 - pad_data_target
            )
            rect_comment_target = QtCore.QRect(
                rect_item.left() + pad_data_target,
                rect_name_target.bottom() + pad_data_target,
                rect_item.width() - pad_data_target,
                rect_data_target.height() - pad_data_target
            )

            # Image half
            painter.save()
            path = QtGui.QPainterPath()
            path.addRect(rect_icon_target)
            painter.setBrush(QtGui.QColor(90, 90, 90))
            painter.drawPath(path)
            painter.restore()

            # Data half
            painter.save()
            path = QtGui.QPainterPath()
            path.setFillRule(QtCore.Qt.WindingFill)
            path.addRect(rect_data_target)

            if option.state & QtWidgets.QStyle.State_Selected:
                painter.setBrush(QtGui.QColor(0, 149, 119))
            elif option.state & QtWidgets.QStyle.State_MouseOver:
                painter.setBrush(QtGui.QColor(100, 100, 100))
            else:
                painter.setBrush(QtGui.QColor(67, 67, 67))

            painter.drawPath(path.simplified())
            painter.restore()

            # Primary Title
            painter.setPen(QtGui.QColor(255, 255, 255))
            name_index = index.data(role=QtCore.Qt.DisplayRole)
            name_font = QtGui.QFont("Segoe UI", 13, QtGui.QFont.DemiBold | QtGui.QFont.NoAntialias)
            painter.setFont(name_font)
            QtWidgets.QApplication.style().drawItemText(painter, rect_name_target, QtCore.Qt.AlignLeft,
                                                        QtWidgets.QApplication.palette(), True,
                                                        name_index)
            # Comment
            painter.setPen(QtGui.QColor(255, 255, 255))
            comment_index = index.data(role=QtCore.Qt.UserRole)
            comment_font = QtGui.QFont("Segoe UI", 13, QtGui.QFont.DemiBold | QtGui.QFont.NoAntialias)
            painter.setFont(comment_font)
            QtWidgets.QApplication.style().drawItemText(painter, rect_comment_target, QtCore.Qt.AlignLeft,
                                                        QtWidgets.QApplication.palette(), True,
                                                        comment_index)

        else:
            return super(FilesItemDelegate, self).paint(painter, option, index)


def launch():
    model_data = {
        'low_poly': {
            '001': {'meta_data': {'name': '001', 'item_type': 'minor', 'comment': 'hey'}},
            '002': {'meta_data': {'name': '002', 'item_type': 'minor', 'comment': 'you'}},
            '003': {'meta_data': {'name': '003', 'item_type': 'minor', 'comment': 'guyyyyys'}},
            '004': {'meta_data': {'name': '004', 'item_type': 'minor', 'comment': "i'll"}},
            '005': {'meta_data': {'name': '005', 'item_type': 'minor', 'comment': 'be'}},
            '006': {'meta_data': {'name': '006', 'item_type': 'minor', 'comment': 'back'}},
            'meta_data': {'item_type': 'major', 'name': 'low_poly', 'major_number': '001'}},
        'high_poly': {
            '001': {'meta_data': {'name': '001', 'item_type': 'minor', 'comment': 'as'}},
            '002': {'meta_data': {'name': '002', 'item_type': 'minor', 'comment': 'you'}},
            '003': {'meta_data': {'name': '003', 'item_type': 'minor', 'comment': 'wish'}},
            '004': {'meta_data': {'name': '004', 'item_type': 'minor', 'comment': "lok"}},
            '005': {'meta_data': {'name': '005', 'item_type': 'minor', 'comment': 'tar'}},
            '006': {'meta_data': {'name': '006', 'item_type': 'minor', 'comment': 'ogar'}},
            'meta_data': {'item_type': 'major', 'name': 'high_poly', 'major_number': '002'}},
        'no_poly': {
            '001': {'meta_data': {'name': '001', 'item_type': 'minor', 'comment': 'this'}},
            '002': {'meta_data': {'name': '002', 'item_type': 'minor', 'comment': 'is'}},
            '003': {'meta_data': {'name': '003', 'item_type': 'minor', 'comment': 'my'}},
            '004': {'meta_data': {'name': '004', 'item_type': 'minor', 'comment': "boomstick"}},
            '005': {'meta_data': {'name': '005', 'item_type': 'minor', 'comment': 'good'}},
            '006': {'meta_data': {'name': '006', 'item_type': 'minor', 'comment': 'bye'}},
            'meta_data': {'item_type': 'major', 'name': 'high_poly', 'major_number': '003'}},
        'meta_data': {'item_type': 'asset', 'name': 'asset1'}
    }


    try:
        os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--enable-logging --log-level=3"
        os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1"  # High dpi setting
        app = QtWidgets.QApplication(sys.argv)

    except:
        pass
    window = MainWidget(model_data)
    window.show()

    try:
        sys.exit(app.exec_())
    except:
        pass

    return window


if __name__ == "__main__":

    launch()
...