【MayaPySide】ちょっとおしゃれなUIメソッド【1日目】

QSS,Tips,StyleSheet,PySide,Tutorial,PySide2,Python,Qt,Maya

こんにちは
MayaPython Advent Calendar 2017の2日目の記事です。
全記事一覧です

【MayaPySide】ちょっとおしゃれなUIメソッド【1日目】
【MayaPySide】ちょっとおしゃれなUIメソッド【2日目】
【MayaPySide】ちょっとおしゃれなUIメソッド【3日目】

最近Twitterで角丸UIを作りたい
おしゃれなUI作りたい
なんて声がよく上がっているのでおしゃれUIの最先端(自称)の私がやってやろうじゃないか!!
ということで前半後半に分けて(予定もっと多くなるかも)作っていきましょう。
今回作るUIはこちら

かっこいい!!おしゃれ!すごい!!キャーーーーー
そんなに褒めないでくださいよー照れるじゃないですか!

はい、ということで作っていきます。

今回作るUIはフレームレスを使ったUIになります
フレームレスにするのはとても簡単なのですが問題点がいくつか出てきます
例えばマウスで移動できない
UIを閉じるボタンがないなど

ですのでマウスで移動できるように設定する、そしてUIを閉じるボタンを作る必要があります。
前半はそれらの設定とWindowを角丸にする設定をしていきます。

ということでひな型を用意します。

from maya import OpenMayaUI
from PySide2 import QtWidgets
from shiboken2 import wrapInstance

class Example(QtWidgets.QMainWindow):
    btn: QtWidgets.QPushButton
    def __init__(self, parent: QtWidgets.QWidget = None) -> None:
        super(Example, self).__init__(parent)
        self.__initUI()
        self.setButton()

    def __initUI(self) -> None:
        self.btn = QtWidgets.QPushButton("Close", self)
        self.setWindowOpacity(0.85)
        self.setGeometry(300, 300, 500, 350)

    def setButton(self) -> None:
        self.btn.move(50, 50)

def main() -> None:
    mayaMainWindow = wrapInstance(int(OpenMayaUI.MQtUtil.mainWindow()), QtWidgets.QMainWindow)
    ex = Example(mayaMainWindow)
    ex.show()

main()

実行するとCloseというボタンが一つある半透明なUIが出てくると思います
Closeと書いているのにクローズできないのでうまく消えるに設定していきます
UIをボタンで閉じるにはclose()を使います

    def setButton(self) -> None:
        self.btn.move(50, 50)
        self.btn.clicked.connect(self.close)

setButtonのなかに記述し、実行してみるとうまくボタンが機能しているかと思います
これでフレームレスにしても安心できます
setWindowFlagsを使用してフレームレスにしてみましょう

from maya import OpenMayaUI
from PySide2 import QtWidgets, QtCore
# ~~~~~~~~~~~~~~~~~~~~~~~~

    def __initUI(self) -> None:
        self.btn = QtWidgets.QPushButton("Close", self)
        self.setWindowOpacity(0.85)
        self.setGeometry(300, 300, 200, 150)
        self.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint)

うまくフレームレスにできました。
しかしこれだと表示した場所から移動させることができません
位置を固定するものであれば問題ないのですが
大体はそうではないのでマウスで移動するイベント処理を入れます
Exampleに入れてもいいのですが今回はクラスの継承を使って導入したいと思います

class FramelessMainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent: QtWidgets.QWidget = None) -> None:
        super(FramelessMainWindow, self).__init__(parent)

# ~~~~~~~~~~~~~~~~~~~~~~~~
class Example(FramelessMainWindow):
    btn: QtWidgets.QPushButton
    def __init__(self, parent: QtWidgets.QWidget = None) -> None:
        super(Example, self).__init__(parent)

FramelessMainWindowというクラスを用意します。
そしてExampleの継承をFramelessMainWindowに変更します
コンストラクタはFramelessMainWindowでmayaをparentしているので空にしておきます。

mouseMoveEvent(QMouseEvent event)
mousePressEvent(QMouseEvent
event)
mouseReleaseEvent(QMouseEvent *event)

マウスで移動するイベント処理は上の3つを使用します
Exampleにイベント処理を入れます


class Example(FramelessMainWindow):
    pos: QtCore.QPoint

    def __init__(self, parent: QtWidgets.QWidget = None) -> None:
# ~~~~~~~~~~~~~~~~~~~~~~~~
    def mouseReleaseEvent(self, event: QtGui.QMouseEvent) -> None:
        self.pos = event.pos()

    def mousePressEvent(self, event: QtGui.QMouseEvent) -> None:
        self.pos = event.pos()

    def mouseMoveEvent(self, event: QtGui.QMouseEvent) -> None:
        winX = event.globalX() - self.pos.x()
        winY = event.globalY() - self.pos.y()
        self.move(winX, winY)

上の処理はQMouseEventを使用しています
event.pos()でマウスのプッシュ、リリース時のマウスカーソル位置情報をself.posに保存し
event.globalX()event.globalY()でイベント発生時のマウスカーソルのグローバル位置情報を取得しています
あとはmove()処理でWindowを移動させるという方法です
これでマウスでwindowを移動することができました
次に角丸のUIを作っていきます
角丸のUIを作るにはpaintEvent内でウィンドウのサイズに合わせたQtGui.QPainterPathを作成し、ウィンドウに見立てたRoundedRectを作成します。その際、バックグラウンドに元地が残るのでsetAutoFillBackgroundQtCore.Qt.WA_TranslucentBackgroundを適応させます。

class FramelessMainWindow(QtWidgets.QMainWindow):
    pos: QtCore.QPoint
    radius: int = 15
    backgroundColorCode: str = "#333333"
    backgroundColor = QtGui.QColor(backgroundColorCode)

    def __init__(self, parent: QtWidgets.QWidget = None) -> None:
        super(FramelessMainWindow, self).__init__(parent)
        self.setAutoFillBackground(True)
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground)

    def paintEvent(self, event: QtGui.QMouseEvent) -> Any:
        painter = QtGui.QPainter(self)
        painter.setRenderHint(QtGui.QPainter.Antialiasing)
        path = QtGui.QPainterPath()
        brush = QtGui.QBrush(self.backgroundColor)
        painter.setBrush(brush)
        path.addRoundedRect(
            0, 0, self.width(), self.height(),
            self.radius, self.radius
        )
        painter.setClipPath(path)
        painter.fillPath(path, painter.brush())
        return super().paintEvent(event)

QPainterPath()はPathを使って図形を作ることができるものです

実行すると下の画像のように変化します

※当時のコードではアンチエイリアスが聞かないマスクの方法を提示していましたがPython3にコードを修正したタイミングでアンチエイリアスの聞く方法に記述していますので画像より見た目がきれいになっているはずです。

せっかくなのでQLinearGradientでグラデーションを適応させてみます


class FramelessMainWindow(QtWidgets.QMainWindow):
    gradient: QtGui.QLinearGradient = QtGui.QLinearGradient()
    gradient.setColorAt(0.0, "#54354e")
    gradient.setColorAt(1.0, "#6a86c7")

    def paintEvent(self, event: QtGui.QMouseEvent) -> Any:
        painter = QtGui.QPainter(self)
        painter.setRenderHint(QtGui.QPainter.Antialiasing)
        path = QtGui.QPainterPath()
        self.gradient.setStart(QtCore.QRectF(self.rect()).topLeft())
        self.gradient.setFinalStop(QtCore. QRectF(self.rect()).topRight())
        brush = QtGui.QBrush(self.gradient)
        painter.setBrush(brush)
        path.addRoundedRect(
            0, 0, self.width(), self.height(),
            self.radius, self.radius
        )
        painter.setClipPath(path)
        painter.fillPath(path, painter.brush())
        return super().paintEvent(event)


こんな感じに色が付けれたら今回は大まかな形や設定ができましたのでここまでとします
【MayaPySide】ちょっとおしゃれなUIメソッド【2日目】の記事で細部を設定していきます


MayaPython Advent Calendar 2017の3日目の記事はhal1932さんのPython で from import を reload する。です