【MayaPySide】ちょっとおしゃれなUIメソッド【2日目】
こんにちはMayaPython Advent Calendar 2017の7日目の記事です
全記事一覧です
【MayaPySide】ちょっとおしゃれなUIメソッド【1日目】
【MayaPySide】ちょっとおしゃれなUIメソッド【2日目】
【MayaPySide】ちょっとおしゃれなUIメソッド【3日目】
ちょっとおしゃれなUIメソッドの二日目です
今回は実は予定していなかったものなのですがフレームレスの需要が高まっている中
Windowのresize
や閉じるときのアニメーションの需要があるようなので今回記事にしました。
改良した
最小のサイズの設定とマスクの適応 pic.twitter.com/HtjoozAVIj— リンゴ酸@ryota unzai (@unpyside) December 5, 2017
リサイズはこんな感じの簡単なものを想定していたのですが
オシャレウィジェットをリサイズできるサンプルコードを置いときますね。https://t.co/0uMR4BJDjz
OceanTeaBaseMixinクラスをつかうとそのままでリサイズできるようになります。
おシャンティを継承しましょう(´ω`) pic.twitter.com/l3v6H6zZc2— ys (@blindys45) December 5, 2017
ysさんが素敵なサンプルを用意してくださったので
それを利用して今回のUIを作成していきます
前回までのコードをベースに作っていきましょう
#!/usr/bin/env python # -*- coding: utf-8 -*- import re import os import sys import maya.cmds as cmds import pymel.core as pm from maya import OpenMayaUI from Qt.QtWidgets import * from Qt.QtGui import * from Qt.QtCore import * try: import shiboken except: import shiboken2 as shiboken ptr = OpenMayaUI.MQtUtil.mainWindow() parent = shiboken.wrapInstance(long(ptr), QWidget) class setMainWindow(QMainWindow): def __init__(self): super(setMainWindow, self).__init__(parent) def mouseReleaseEvent(self, pos): self.mc_x = pos.x() self.mc_y = pos.y() def mousePressEvent(self, pos): self.mc_x = pos.x() self.mc_y = pos.y() def mouseMoveEvent(self, pos): winX = pos.globalX() - self.mc_x winY = pos.globalY() - self.mc_y self.move(winX, winY) class Example(setMainWindow): def __init__(self): super(Example, self).__init__() self.initUI() self.setButton() self.paletteUI() def initUI(self): self.setWindowOpacity(0.85) self.setGeometry(300, 300, 200, 150) self.setWindowFlags(Qt.Window | Qt.FramelessWindowHint) def setButton(self): btn = QPushButton("Close", self) btn.move(50,50) btn.clicked.connect(self.close) def paletteUI(self): setColors = ['#54354e', '#6a86c7'] Palette = QPalette() gradient = QLinearGradient(QRectF( self.rect()).topLeft(), QRectF(self.rect()).topRight() ) gradient.setColorAt(0.0, setColors[0]) gradient.setColorAt(1.0, setColors[1]) Palette.setBrush(QPalette.Background, QBrush(gradient)) self.setPalette(Palette) path = QPainterPath() path.addRoundedRect(self.rect(), 10, 10) region = QRegion(path.toFillPolygon().toPolygon()) self.setMask(region) def Example_UI(): global Example_UI_ex app = QApplication.instance() Example_UI_ex = Example() Example_UI_ex.show() sys.exit() app.exec_() Example_UI()
まず最初にクローズボタンのカスタマイズからしていきましょう
クローズボタンのカスタマイズは閉じる際にフェードアウトして消えていく、というアニメーションを付けます
使用するのはQTimer
、QPropertyAnimation
ですQTimer
というのは繰り返すとシングルショットタイマーを使用することができるものです
1000の単位で1秒となります。
では作っていきましょう。
まず最初にCloseButton専用のclassを作っていきます
class SysButton(QPushButton): closed = Signal() def __init__(self): super(SysButton, self).__init__() self.clicked.connect(self.__closed) def __closed(self): self.closed.emit()
PySideにはシグナルとスロットというものがあります。
シグナルclosedを作成し、Close専用のQPushButtonを用意しました。
次にWidgetを用意します
class MainWidget(QWidget): closed = Signal() def __init__(self): super(MainWidget, self).__init__() self.sysButton = SysButton() self.sysButton.setText('Close') self.sysButton.closed.connect(self.__close) self.mainLayout = QVBoxLayout(self) self.mainLayout.addWidget(self.sysButton) self.mainLayout.addStretch(True) def __close(self): self.closed.emit()
作成したWidgetを前回作ったsetButton
をsetWindget
名前を変え、Widgetを埋め込むものに作り替えます
具体的にはclass Example(setMainWindow)
のCentralWidget
にMainWidget
を埋め込みます
そしてgui_Close
`という関数を作ります
内容はQTimer
は0.6秒後にWindowをCloseさせ、QPropertyAnimation
は0.6秒かけてWindowのOpacityを0にするという機能です
そしてその機能とSignal
をコネクションします。
def setWidget(self): mainWidget = MainWidget() mainWidget.closed.connect(self.gui_Close) self.setCentralWidget(mainWidget) def gui_Close(self): self.timer = QTimer() self.timer.setInterval(600) self.timer.timeout.connect(self.close) self.timer.start() self.fade = QPropertyAnimation(self, "windowOpacity") self.fade.setStartValue(0.85) self.fade.setEndValue(0.0) self.fade.setKeyValueAt(0.5, 0.0) self.fade.setEasingCurve(QEasingCurve.InOutCubic) self.fade.setDuration(600) self.fade.start()
次にWindowをリサイズする機能をsetMainWindowに追加していきます
class setMainWindow(QMainWindow): def __init__(self): super(setMainWindow, self).__init__(parent) self.installEventFilter(self) self.resize_mode = None def mouseReleaseEvent(self, pos): self.mc_x = pos.x() self.mc_y = pos.y() def mousePressEvent(self, pos): self.mc_x = pos.x() self.mc_y = pos.y() borderWidth = 8 resizeWidth = self.minimumWidth() != self.maximumWidth() resizeHeight = self.minimumHeight() != self.maximumHeight() self.pre_size_x = self.size().width() self.pre_size_y = self.size().height() self.size_w = self.size().width() self.size_h = self.size().height() self.sub_w = self.size_w - self.mc_x self.sub_h = self.size_h - self.mc_y resize_mode = None if resizeWidth is True: if self.mc_x < borderWidth: resize_mode = "left" if self.sub_w < borderWidth: resize_mode = "right" if resizeHeight is True: if self.mc_y < borderWidth: resize_mode = "top" if self.sub_h < borderWidth: resize_mode = "bottom" if resizeWidth is True and resizeHeight is True: if self.mc_x <= borderWidth and self.mc_y <= borderWidth: resize_mode = 'top_left' if self.sub_w <= borderWidth and self.sub_h <= borderWidth: resize_mode = 'bottom_right' if self.sub_w <= borderWidth and self.mc_y <= borderWidth: resize_mode = 'top_right' if self.mc_x <= borderWidth and self.sub_h <= borderWidth: resize_mode = 'bottom_left' if self.mc_x >= borderWidth and self.mc_y >= borderWidth\ and self.sub_w >= borderWidth\ and self.sub_h >= borderWidth: resize_mode = 'center' self.resize_mode = resize_mode return self.resize_mode def eventFilter(self, obj, event): if event.type() == QEvent.Type.Enter: QApplication.setOverrideCursor(Qt.ArrowCursor) if event.type() == QEvent.Type.HoverMove: pos = event.pos() cur_dict = { 'right': Qt.SizeHorCursor, 'left': Qt.SizeHorCursor, 'top': Qt.SizeVerCursor, 'bottom': Qt.SizeVerCursor, 'top_left': Qt.SizeFDiagCursor, 'bottom_right': Qt.SizeFDiagCursor, 'top_right': Qt.SizeBDiagCursor, 'bottom_left': Qt.SizeBDiagCursor, 'center': Qt.ArrowCursor, None: Qt.ArrowCursor } cur = cur_dict[self.resize_mode] current_cur = QApplication.overrideCursor().shape() cur_set = cur_dict[self.resize_mode] if cur != current_cur: QApplication.changeOverrideCursor(cur_set) if event.type() == QEvent.Type.Leave: QApplication.restoreOverrideCursor() def mouseMoveEvent(self, pos): self.win_x = pos.globalX() - self.mc_x self.win_y = pos.globalY() - self.mc_y if self.resize_mode is None: self.resize_mode = 'center' if self.resize_mode is 'center': self.move(self.win_x, self.win_y) else: self.re_w = self.size().width() self.re_h = self.size().height() if 'right' in self.resize_mode: self.re_w = pos.x() + self.sub_w self.resize(self.re_w, self.re_h) if 'bottom' in self.resize_mode: self.re_h = pos.y() + self.sub_h self.resize(self.re_w, self.re_h) if 'left' in self.resize_mode: self.resub_w = pos.x() - self.mc_x self.re_w = self.re_w - self.resub_w self.resize(self.re_w, self.re_h) if self.size().width() != self.pre_size_x: self.win_x = pos.globalX() - self.mc_x self.move(self.win_x, self.pos().y()) self.pre_size_x = self.size().width() if 'top' in self.resize_mode: self.resub_h = pos.y() - self.mc_y self.re_h = self.re_h - self.resub_h self.resize(self.re_w, self.re_h) if self.size().height() != self.pre_size_y: self.win_y = pos.globalY() - self.mc_y self.move(self.pos().x(), self.win_y) self.pre_size_y = self.size().height()
def mousePressEvent(self, pos)
には上下左右斜のそれぞれ端から8pxの位置にマウスを押した場合、どの位置にあるかを取得する機能を設定しています
そして、def mouseMoveEvent(self, pos)
でどの位置にあるかによってresize処理をどう実行させるかを設定しています。
また、PySideはeventFilter関数
をつかうことでeventを受け取れる仕組みがあります。
eventを受け取るボタンをinstallEventFilter
という関数でeventに登録し、event関数である eventFilter
を定義し、そのeventを監視することができますdef eventFilter(self, obj, event)
はWindowをResizeする際にマウスの見た目を変更する処理を入れていますdef eventFilter(self, obj, event)
を登録するためにself.installEventFilter(self)
を読み込んでいます。
しかし、このまま実行するとUIがおかしくなります
なぜかというとmaskのサイズが変更されていないからです
ですのでサイズを大きくすれば変に伸びて、形が切れていきます
これを防ぐためにdef paletteUI(self)
をベースに作った、maskUIという新しいクラスを用意します
class maskUI(QMainWindow): def __init__(self): super(maskUI, self).__init__(parent) def PaletteUI(self): setColors = ['#54354e', '#6a86c7'] Palette = QPalette() gradient = QLinearGradient(QRectF( self.rect()).topLeft(), QRectF(self.rect()).topRight() ) gradient.setColorAt(0.0, setColors[0]) gradient.setColorAt(1.0, setColors[1]) Palette.setBrush(QPalette.Background, QBrush(gradient)) path = QPainterPath() path.addRoundedRect(self.rect(), 3, 3) region = QRegion(path.toFillPolygon().toPolygon()) self.setMask(region) self.setPalette(Palette) class setMainWindow(maskUI): def __init__(self): super(setMainWindow, self).__init__()
内容は一緒です
そしてsetMainWindow
の継承をmaskUIに変更します
コンストラクタはsetMainWindow
でmayaをparentしているので空にしておきます。
そして、PaletteUIをmouseMoveEvent
、Exmple、それぞれに読み込みます
内容は一緒です
def mouseMoveEvent(self, pos): self.PaletteUI() # ~~~~~~~~~~~~~~~~~~~~~~~~ class Example(setMainWindow): def __init__(self): super(Example, self).__init__() self.initUI() self.setWidget() self.PaletteUI()
次回はUIのデザイン部分をしていきます。
追記
イベントをフィルタリングする場合Trueを返し、それ以外の場合はFalseを返さないといけません
その処理を入れ忘れていたため、# RuntimeWarning:が出ていました
そのため、def eventFilter(self, obj, event)の処理を以下に変更する必要があります
def eventFilter(self, obj, event): if event.type() == QEvent.Type.Enter: QApplication.setOverrideCursor(Qt.ArrowCursor) return True if event.type() == QEvent.Type.HoverMove: pos = event.pos() cur_dict = { 'right': Qt.SizeHorCursor, 'left': Qt.SizeHorCursor, 'top': Qt.SizeVerCursor, 'bottom': Qt.SizeVerCursor, 'top_left': Qt.SizeFDiagCursor, 'bottom_right': Qt.SizeFDiagCursor, 'top_right': Qt.SizeBDiagCursor, 'bottom_left': Qt.SizeBDiagCursor, 'center': Qt.ArrowCursor, None: Qt.ArrowCursor } cur = cur_dict[self.resize_mode] current_cur = QApplication.overrideCursor().shape() cur_set = cur_dict[self.resize_mode] if cur != current_cur: QApplication.changeOverrideCursor(cur_set) return True return True if event.type() == QEvent.Type.Leave: QApplication.restoreOverrideCursor() return True else: return False
【MayaPySide】ちょっとおしゃれなUIメソッド【3日目】の記事でUIの見た目を設定していきます
MayaPython Advent Calendar 2017の8日目の記事はSEN_AさんのMaya Python API 2.0 を使ってCurveからclosestPointを取得するノードを作ろう!です
ディスカッション
コメント一覧
まだ、コメントがありません